first_commit

This commit is contained in:
2026-01-12 12:43:50 +01:00
parent c75b3e9563
commit 69e186a7f1
15289 changed files with 1616360 additions and 1944 deletions
+181
View File
@@ -0,0 +1,181 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Package Manager
**Always use `yarn` instead of `npm`** for all commands in this repository (yarn@4.9.2).
## Common Commands
### Development
- `yarn install` - Install dependencies (run from project root)
- `yarn start` - Start the docs-site development server at http://localhost:5173
- `yarn build` - Full production build (JS + CSS in all formats)
- `yarn build-dev` - Development build with file watching
### Testing and Quality
- `yarn test` - Run the full test suite (Jest with ts-jest)
- `yarn test src/test/calendar_test.test.tsx` - Run a single test file
- `yarn test:watch` - Run tests in watch mode
- `yarn test:ci` - Run tests with coverage for CI
- `yarn lint` - Run both ESLint and Stylelint
- `yarn eslint` - Run ESLint on src directory
- `yarn sass-lint` - Run Stylelint on SCSS files
- `yarn type-check` - Run TypeScript type checking without emitting files
- `yarn prettier` - Format all JS/JSX/TS/TSX files
- `yarn prettier:check` - Check formatting without making changes
### Building Individual Pieces
- `yarn build:src` - Build JS using Rollup
- `yarn js:dev` - Build JS in watch mode
- `yarn css:prod` - Build minified production CSS
- `yarn css:dev` - Build expanded development CSS
- `yarn css:modules:prod` - Build CSS modules (minified)
- `yarn css:modules:dev` - Build CSS modules (expanded)
## Architecture Overview
### Component Hierarchy
The main entry point is `src/index.tsx` which exports the `DatePicker` component. The component hierarchy flows as follows:
```
DatePicker (index.tsx) - Main component, class-based
├── PopperComponent (popper_component.tsx) - Positioned calendar container
│ ├── withFloating HOC (with_floating.tsx) - Floating UI integration
│ ├── Portal (portal.tsx) - Optional portal rendering
│ └── TabLoop (tab_loop.tsx) - Keyboard navigation wrapper
│ └── Calendar (calendar.tsx) - Core calendar logic
│ ├── ClickOutsideWrapper - Handles outside clicks
│ ├── Month/Year/Time components - Date selection UI
│ └── Various dropdowns for date navigation
```
### Key Architectural Patterns
**Positioning System**: The datepicker uses `@floating-ui/react` (v0.27.15) for positioning the calendar relative to the input. The `withFloating` HOC wraps `PopperComponent` to provide positioning logic.
**Date Utilities**: All date manipulation goes through `date_utils.ts`, which wraps `date-fns` (v4.1.0). This provides a consistent API across the codebase and makes it easier to maintain date-related logic.
**State Management**: The main `DatePicker` component is class-based and manages state internally. Most child components are controlled components that receive props and callbacks.
**Styling**: SCSS source files live in `src/stylesheets/`. The build process generates multiple CSS outputs:
- Regular CSS: `react-datepicker.css` (dev) and `react-datepicker.min.css` (prod)
- CSS Modules: `react-datepicker-cssmodules.css` and `react-datepicker.module.css`
### Build Output
Rollup (configured in `rollup.config.mjs`) generates multiple bundle formats in the `dist/` directory:
- **UMD**: `react-datepicker.js` and `react-datepicker.min.js` (browser)
- **CommonJS**: `index.js` (Node/bundlers)
- **ES Modules**: `index.es.js` (modern bundlers)
- **Types**: `index.d.ts` (TypeScript definitions)
### Testing Architecture
Tests use Jest with `@testing-library/react` and are located in `src/test/`. The test setup:
- Configuration: `jest.config.js`
- Setup file: `src/test/index.ts`
- Helper components: `src/test/helper_components/`
- Coverage is collected and reported to Codecov
**Important for tests**: Some components use ShadowDOM for testing. The `shadow_root.tsx` helper uses `flushSync` to ensure synchronous updates required by tests.
### Floating UI Integration Notes
The codebase uses `@floating-ui/react` for positioning. **Important**: The Floating UI library requires refs and context to be accessed during render, which is by design. When fixing linting errors:
- Use `eslint-disable` comments for Floating UI ref accesses (e.g., `popperProps.refs.setFloating`, `popperProps.context`, `arrowRef`)
- These are **not** violations of React best practices—they're intentional library usage
- See `popper_component.tsx` and `with_floating.tsx` for examples
### React Hooks Rules
This codebase uses `eslint-plugin-react-hooks` v7.0.1+ which has strict rules about:
- **Ref access during render**: Generally not allowed, but see Floating UI exception above
- **setState in effects**: Avoid calling setState directly in effects; use `flushSync` when synchronous updates are needed
- When refs must be updated based on props, do it in `useEffect`, not during render
## Development Workflow
### Local Development Setup (Full)
1. Install node >=16.0.0 and yarn >=4.6.x
2. `yarn install` from project root
3. `yarn build` from project root (generates dist/ directory)
4. `yarn start` to launch docs at http://localhost:5173
5. In a new terminal, run `yarn build-dev` to auto-rebuild on changes
**Note**: The docs-site uses a portal: dependency (`"react-datepicker": "portal:../"`) which links to the parent project. Changes to the main package are reflected in the docs when you rebuild.
### Alternative Setup with yarn link (from CONTRIBUTING.md)
If you need tighter integration during development:
1. Run `yarn link` from project root
2. Run `cd docs-site && yarn link react-datepicker`
3. Then follow steps above
### Quick Development Workflow
After initial setup, when making changes:
- **JS/TS changes**: Changes auto-rebuild if `yarn build-dev` is running
- **SCSS changes**: Run `yarn run css:dev && yarn run css:modules:dev`
### Pre-commit Hooks
The repo uses Husky with lint-staged. On commit:
- Prettier formats staged files automatically
- Files are automatically added to the commit (via `git add` in lint-staged config)
## Dependencies
### Core Runtime Dependencies
- `react` and `react-dom` (^16.9.0 || ^17 || ^18 || ^19) - peer dependencies
- `date-fns` (^4.1.0) - Date manipulation library
- `@floating-ui/react` (^0.27.15) - Positioning engine
- `clsx` (^2.1.1) - Conditional className utility
### Important Dev Tools
- **Build**: Rollup with Babel and TypeScript plugins
- **Testing**: Jest, ts-jest, @testing-library/react, jest-axe
- **Linting**: ESLint 9, TypeScript ESLint, Stylelint
- **Formatting**: Prettier 3.4.2
- **CSS**: Sass 1.93.2
## Bug Fix Workflow (TDD)
When fixing bugs, always follow Test-Driven Development:
1. **Write the test first** - Create a failing test that reproduces the bug
2. **Confirm it fails** - Run the test to verify it captures the broken behavior
3. **Implement the fix** - Make the minimal code change to fix the issue
4. **Verify the test passes** - Run the test again to confirm the fix works
5. **Run full test suite** - Ensure no regressions with `yarn test:ci`
6. **Create a branch** - `git checkout -b fix/descriptive-branch-name`
7. **Commit the fix** - Use a descriptive commit message referencing the issue (e.g., `fix: description of fix\n\nFixes #123`)
8. **Push the branch** - `git push -u origin fix/descriptive-branch-name`
9. **Create a PR** - Use `gh pr create` with a clear title and description that references the issue
10. **Return to main** - `git checkout main` to prepare for the next task
This ensures every bug fix has regression coverage and documents the expected behavior.
## Code Conventions
- **Prettier handles all code formatting** - don't worry about tabs vs spaces
- **ESLint enforces coding standards** - run `yarn lint` before committing
- **TypeScript strict mode** - the codebase is fully typed
- **Tests are required** - add Jest tests for new functionality
- **Accessibility matters** - maintain ARIA attributes and keyboard navigation
+46
View File
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@reactdatepicker.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/
+41
View File
@@ -0,0 +1,41 @@
# How to contribute
Thanks for taking your time to read this. We're thrilled you're reading this because we the help from the community to keep improving this project.
## Testing
We use Jest with React Testing Library for our test suite. Please write tests for new code you create.
## Submitting changes
Please send a [GitHub Pull Request](https://github.com/Hacker0x01/react-datepicker/pull/new/main) with a clear list of what you've done (read more about [pull requests](https://help.github.com/articles/about-pull-requests/)). When you send a pull request, we will love you forever if you include a test to cover your changes. We can always use more test coverage.
Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this:
\$ git commit -m "A summary of the commit > > A paragraph describing what changed and its impact."
All pull requests are reviewed with :heart: by [PullRequest](https://www.pullrequest.com/).
The GitHub user persona will be displayed as "pullrequest (bot)" but the written contents are from a human software engineer. You can respond to these comments as if they were any other GitHub user. More [here](https://docs.pullrequest.com/customer-documentation/assign-code-review-to-pull-request-network/collaborating-with-pullrequest-reviewers#addressing-pullrequest-reviewers-in-comments).
## Coding conventions
Start reading our code, and you'll get the hang of it. We optimize for readability:
- We use prettier for code styling. Don't worry about tabs vs spaces, or how to indent your code.
- We use ESlint for all other coding standards. We try to be consistent and helpful.
- This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers, the goal is to make the ride as smooth as possible.
## Getting set up
Local development configuration is pretty snappy. Here's how to get set up:
1. Install/use node >=16.0.0
1. Install/use yarn >=4.6.x
1. Run `yarn install` from project root
1. Run `yarn build` from project root
1. Run `yarn start` from project root (This command launches a documentation app and runs it as a simple webserver at http://localhost:5173.)
1. Open a new terminal window
1. Run `yarn build-dev` from project root (sets up watch mode that auto-rebuilds on file changes)
You can run `yarn test` to execute the test suite and linters. To help you develop the component weve set up some tests that cover the basic functionality (can be found in `/tests`). Even though were big fans of testing, this only covers a small piece of the component. We highly recommend you add tests when youre adding new functionality.
1. After each JS change run `yarn build:js` in project root
1. After each SCSS change run `yarn run css:dev && yarn run css:modules:dev` in project root
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2025 HackerOne Inc and individual contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+203
View File
@@ -0,0 +1,203 @@
# React Date Picker
[![npm version](https://badge.fury.io/js/react-datepicker.svg)](https://badge.fury.io/js/react-datepicker)
[![Test suite](https://github.com/Hacker0x01/react-datepicker/actions/workflows/test.yml/badge.svg)](https://github.com/Hacker0x01/react-datepicker/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/Hacker0x01/react-datepicker/branch/main/graph/badge.svg)](https://codecov.io/gh/Hacker0x01/react-datepicker)
[![Downloads](https://img.shields.io/npm/dm/react-datepicker.svg)](https://npmjs.org/package/react-datepicker)
A simple and reusable Datepicker component for React ([Demo](https://reactdatepicker.com/))
![](https://cloud.githubusercontent.com/assets/1412392/5339491/c40de124-7ee1-11e4-9f07-9276e2545f27.png)
## Installation
The package can be installed via [npm](https://github.com/npm/cli):
```
npm install react-datepicker --save
```
Or via [yarn](https://github.com/yarnpkg/yarn):
```
yarn add react-datepicker
```
Youll need to install React and PropTypes separately since those dependencies arent included in the package. If you need to use a locale other than the default en-US, you'll also need to import that into your project from date-fns (see Localization section below). Below is a simple example of how to use the Datepicker in a React view. You will also need to require the CSS file from this package (or provide your own). The example below shows how to include the CSS from this package if your build system supports requiring CSS files (Webpack is one that does).
```js
import React, { useState } from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
// CSS Modules, react-datepicker-cssmodules.css
// import 'react-datepicker/dist/react-datepicker-cssmodules.css';
const Example = () => {
const [startDate, setStartDate] = useState(new Date());
return <DatePicker selected={startDate} onChange={(date) => setStartDate(date)} />;
};
```
## Configuration
The most basic use of the DatePicker can be described with:
```js
<DatePicker selected={startdate} onChange={(date) => setStartDate(date)} />
```
You can use `onSelect` event handler which fires each time some calendar date has been selected
```js
<DatePicker
selected={date}
onSelect={handleDateSelect} //when day is clicked
onChange={handleDateChange} //only when value has changed
/>
```
`onClickOutside` handler may be useful to close datepicker in `inline` mode
See [here](https://github.com/Hacker0x01/react-datepicker/blob/main/docs/datepicker.md) for a full list of props that may be passed to the component. Examples are given on the [main website](https://hacker0x01.github.io/react-datepicker).
### Working with Examples
When using examples from the documentation site, note that they may reference utilities from external libraries. Common imports you might need:
**Date manipulation** (from `date-fns`):
```js
import { getYear, getMonth, addDays, subDays, setHours, setMinutes } from "date-fns";
```
**Utility functions**:
- For `range()` function used in custom headers: `import range from "lodash/range";`
- Or implement your own: `const range = (start, end, step) => Array.from({ length: (end - start) / step }, (_, i) => start + i * step);`
**TypeScript types**:
```ts
import type { ReactDatePickerCustomHeaderProps } from "react-datepicker";
```
All examples on the documentation site include commented import statements at the top showing exactly what you need to import for your own project.
For a comprehensive guide on imports, see the [Common Imports Guide](https://github.com/Hacker0x01/react-datepicker/blob/main/docs/imports-guide.md).
### Time picker
You can also include a time picker by adding the showTimeSelect prop
```js
<DatePicker selected={date} onChange={handleDateChange} showTimeSelect dateFormat="Pp" />
```
Times will be displayed at 30-minute intervals by default (default configurable via timeIntervals prop)
More examples of how to use the time picker are given on the [main website](https://hacker0x01.github.io/react-datepicker)
### Localization
The date picker relies on [date-fns internationalization](https://date-fns.org/v3.3.1/docs/I18n) to localize its display components. By default, the date picker will use the locale globally set, which is English. Provided are 3 helper methods to set the locale:
- **registerLocale** (string, object): loads an imported locale object from date-fns
- **setDefaultLocale** (string): sets a registered locale as the default for all datepicker instances
- **getDefaultLocale**: returns a string showing the currently set default locale
```js
import { registerLocale, setDefaultLocale } from "react-datepicker";
import { es } from 'date-fns/locale/es';
registerLocale('es', es)
<DatePicker
locale="es"
/>
```
Locales can be changed in the following way:
- **Globally** - `setDefaultLocale('es');`
### Timezone handling
React-datepicker uses native JavaScript Date objects which are timezone-aware. By default, dates are displayed in the user's local timezone. The library does not include built-in timezone conversion utilities.
**Common issue: "Date is one day off" ([#1018](https://github.com/Hacker0x01/react-datepicker/issues/1018))** - If you're seeing dates shift by one day when converting to ISO strings or sending to a server, this is due to timezone conversion, not a bug. See the [Timezone Handling Guide](https://github.com/Hacker0x01/react-datepicker/blob/main/docs/timezone.md#the-date-is-one-day-off-problem-issue-1018) for solutions.
For detailed information about working with timezones, UTC dates, and common timezone-related scenarios, see the [Timezone Handling Guide](https://github.com/Hacker0x01/react-datepicker/blob/main/docs/timezone.md).
For applications requiring timezone conversion, we recommend using [date-fns-tz](https://github.com/marnusw/date-fns-tz) alongside react-datepicker.
## Compatibility
### React
We're always trying to stay compatible with the latest version of React. We can't support all older versions of React.
Latest compatible versions:
- React 16 or newer: React-datepicker v2.9.4 and newer
- React 15.5: React-datepicker v2.9.3
- React 15.4.1: needs React-datepicker v0.40.0, newer won't work (due to react-onclickoutside dependencies)
- React 0.14 or newer: All above React-datepicker v0.13.0
- React 0.13: React-datepicker v0.13.0
- pre React 0.13: React-datepicker v0.6.2
### Moment.js
Up until version 1.8.0, this package was using Moment.js. Starting v2.0.0, we switched to using `date-fns`, which uses native Date objects, to reduce the size of the package. If you're switching from 1.8.0 to 2.0.0 or higher, please see the updated example above of check out the [examples site](https://reactdatepicker.com) for up to date examples.
### Browser Support
The date picker is compatible with the latest versions of Chrome, Firefox, and IE10+.
Unfortunately, it is difficult to support legacy browsers while maintaining our ability to develop new features in the future. For IE9 support, it is known that the [classlist polyfill](https://www.npmjs.com/package/classlist-polyfill) is needed, but this may change or break at any point in the future.
## Local Development
The `main` branch contains the latest version of the Datepicker component.
To begin local development:
1. Run `yarn install` from project root
2. Run `yarn build` from project root
3. Run `yarn start` from project root
The last step starts documentation app as a simple webserver on http://localhost:5173.
You can run `yarn test` to execute the test suite and linters. To help you develop the component weve set up some tests that cover the basic functionality (can be found in `/tests`). Even though were big fans of testing, this only covers a small piece of the component. We highly recommend you add tests when youre adding new functionality.
Please refer to `CONTRIBUTING.md` file for more details about getting set up.
### The examples
The examples are hosted within the docs folder and are ran in the simple app that loads the Datepicker. To extend the examples with a new example, you can simply duplicate one of the existing examples and change the unique properties of your example.
## Accessibility
### Keyboard support
- _Left_: Move to the previous day.
- _Right_: Move to the next day.
- _Up_: Move to the previous week.
- _Down_: Move to the next week.
- _PgUp_: Move to the previous month.
- _Shift+PgUp_: Move to the same day and month of the previous year. If that day does not exist, moves focus to the last day of the month.
- _PgDn_: Move to the next month.
- _Shift+PgDn_: Move to the same day and month of the next year. If that day does not exist, moves focus to the last day of the month.
- _Home_: Move to the first day (e.g Sunday) of the current week.
- _End_: Move to the last day (e.g. Saturday) of the current week.
- _Enter/Esc/Tab_: close the calendar. (Enter & Esc calls preventDefault)
#### For month picker
- _Left_: Move to the previous month.
- _Right_: Move to the next month.
- _Enter_: Select date and close the calendar
## License
Copyright (c) 2014-2025 HackerOne Inc. and individual contributors. Licensed under MIT license, see [LICENSE](LICENSE) for the full license.
+35
View File
@@ -0,0 +1,35 @@
# Brand Promise
Keeping user information safe and secure is a top priority, and we welcome the contribution of external security researchers.
# Scope
If you believe you've found a security issue in software that is maintained in this repository, we encourage you to notify us.
| Version | In scope | Source code |
| ------- | -------- | ----------- |
| >= 6.0.0 | ✅ | https://github.com/Hacker0x01/react-datepicker |
| < 2.0.0 | ❌ | https://github.com/Hacker0x01/react-datepicker/releases |
# How to Submit a Report
To submit a vulnerability report, please fill out this [form](https://hackerone.com/security). Your submission will be reviewed and validated by a member of our team.
# Safe Harbor
We support safe harbor for security researchers who:
* Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our services.
* Only interact with accounts you own or with explicit permission of the account holder. If you do encounter Personally Identifiable Information (PII) contact us immediately, do not proceed with access, and immediately purge any local information.
* Provide us with a reasonable amount of time to resolve vulnerabilities prior to any disclosure to the public or a third-party.
We will consider activities conducted consistent with this policy to constitute "authorized" conduct and will not pursue civil action or initiate a complaint to law enforcement. We will help to the extent we can if legal action is initiated by a third party against you.
Please submit a report to us before engaging in conduct that may be inconsistent with or unaddressed by this policy.
# Preferences
* Please provide detailed reports with reproducible steps and a clearly defined impact.
* Include the version number of the vulnerable package in your report
* Social engineering (e.g. phishing, vishing, smishing) is prohibited.
+190
View File
@@ -0,0 +1,190 @@
import React, { Component } from "react";
import { type Locale } from "./date_utils";
import InputTime from "./input_time";
import Month from "./month";
import MonthDropdown from "./month_dropdown";
import MonthYearDropdown from "./month_year_dropdown";
import Time from "./time";
import Year from "./year";
import YearDropdown from "./year_dropdown";
import type { ClickOutsideHandler } from "./click_outside_wrapper";
import type { Day } from "date-fns";
interface YearDropdownProps extends React.ComponentPropsWithoutRef<typeof YearDropdown> {
}
interface MonthDropdownProps extends React.ComponentPropsWithoutRef<typeof MonthDropdown> {
}
interface MonthYearDropdownProps extends React.ComponentPropsWithoutRef<typeof MonthYearDropdown> {
}
interface YearProps extends React.ComponentPropsWithoutRef<typeof Year> {
}
interface MonthProps extends React.ComponentPropsWithoutRef<typeof Month> {
}
interface TimeProps extends React.ComponentPropsWithoutRef<typeof Time> {
}
interface InputTimeProps extends React.ComponentPropsWithoutRef<typeof InputTime> {
}
export declare const OUTSIDE_CLICK_IGNORE_CLASS = "react-datepicker-ignore-onclickoutside";
export interface ReactDatePickerCustomHeaderProps {
date: CalendarState["date"];
customHeaderCount: number;
monthDate: Date;
changeMonth: (month: number) => void;
changeYear: (year: number) => void;
decreaseMonth: VoidFunction;
increaseMonth: VoidFunction;
decreaseYear: VoidFunction;
increaseYear: VoidFunction;
prevMonthButtonDisabled: boolean;
nextMonthButtonDisabled: boolean;
prevYearButtonDisabled: boolean;
nextYearButtonDisabled: boolean;
visibleYearsRange?: {
startYear: number;
endYear: number;
};
}
export interface ReactDatePickerCustomDayNameProps {
day: Date;
shortName: string;
fullName: string;
locale?: Locale;
customDayNameCount: number;
}
type CalendarProps = React.PropsWithChildren<Omit<YearDropdownProps, "date" | "onChange" | "year" | "minDate" | "maxDate"> & Omit<MonthDropdownProps, "month" | "onChange"> & Omit<MonthYearDropdownProps, "date" | "onChange" | "minDate" | "maxDate"> & Omit<YearProps, "onDayClick" | "selectingDate" | "clearSelectingDate" | "onYearMouseEnter" | "onYearMouseLeave" | "minDate" | "maxDate"> & Omit<MonthProps, "ariaLabelPrefix" | "onChange" | "day" | "onDayClick" | "handleOnKeyDown" | "handleOnMonthKeyDown" | "onDayMouseEnter" | "onMouseLeave" | "orderInDisplay" | "monthShowsDuplicateDaysEnd" | "monthShowsDuplicateDaysStart" | "minDate" | "maxDate"> & Omit<TimeProps, "onChange" | "format" | "intervals" | "monthRef"> & Omit<InputTimeProps, "date" | "timeString" | "onChange"> & {
selectsRange?: boolean;
startDate?: Date | null;
endDate?: Date | null;
className?: string;
container?: React.ElementType;
showYearPicker?: boolean;
showMonthYearPicker?: boolean;
showQuarterYearPicker?: boolean;
showTimeSelect?: boolean;
showTimeInput?: boolean;
showYearDropdown?: boolean;
showMonthDropdown?: boolean;
yearItemNumber?: number;
useWeekdaysShort?: boolean;
forceShowMonthNavigation?: boolean;
showDisabledMonthNavigation?: boolean;
formatWeekDay?: (date: string) => string;
onDropdownFocus?: (event: React.FocusEvent<HTMLDivElement>) => void;
calendarStartDay?: Day;
weekDayClassName?: (date: Date) => string;
onMonthChange?: (date: Date) => void;
onYearChange?: (date: Date) => void;
onDayMouseEnter?: (date: Date) => void;
onMonthMouseLeave?: VoidFunction;
weekLabel?: string;
onClickOutside: ClickOutsideHandler;
outsideClickIgnoreClass?: string;
previousMonthButtonLabel?: React.ReactNode;
previousYearButtonLabel?: React.ReactNode;
previousMonthAriaLabel?: string;
previousYearAriaLabel?: string;
nextMonthButtonLabel?: React.ReactNode;
nextYearButtonLabel?: React.ReactNode;
nextMonthAriaLabel?: string;
nextYearAriaLabel?: string;
showPreviousMonths?: boolean;
monthsShown?: number;
monthSelectedIn?: number;
onMonthSelectedInChange?: (monthSelectedIn: number) => void;
onSelect: (day: Date, event?: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>, monthSelectedIn?: number) => void;
renderCustomHeader?: (props: ReactDatePickerCustomHeaderProps) => React.ReactElement;
renderCustomDayName?: (props: ReactDatePickerCustomDayNameProps) => React.ReactNode;
monthHeaderPosition?: "top" | "middle" | "bottom";
onYearMouseEnter?: YearProps["onYearMouseEnter"];
onYearMouseLeave?: YearProps["onYearMouseLeave"];
monthAriaLabelPrefix?: MonthProps["ariaLabelPrefix"];
handleOnDayKeyDown?: MonthProps["handleOnKeyDown"];
handleOnKeyDown?: (event: React.KeyboardEvent<HTMLDivElement> | React.KeyboardEvent<HTMLLIElement> | React.KeyboardEvent<HTMLButtonElement>) => void;
onTimeChange?: (time: Date, modifyDateType?: "start" | "end") => void;
timeFormat?: TimeProps["format"];
timeIntervals?: TimeProps["intervals"];
} & (({
showMonthYearDropdown: true;
} & Pick<YearDropdownProps, "maxDate" | "minDate">) | ({
showMonthYearDropdown?: never;
} & Pick<YearDropdownProps, "maxDate" | "minDate"> & Pick<YearProps, "maxDate" | "minDate"> & Pick<MonthProps, "maxDate" | "minDate">))>;
interface CalendarState extends Pick<YearProps, "selectingDate">, Pick<MonthProps, "selectingDate"> {
date: Required<YearProps>["date"];
monthContainer: TimeProps["monthRef"];
isRenderAriaLiveMessage: boolean;
}
export default class Calendar extends Component<CalendarProps, CalendarState> {
static get defaultProps(): {
monthsShown: number;
forceShowMonthNavigation: boolean;
outsideClickIgnoreClass: string;
timeCaption: string;
previousYearButtonLabel: string;
nextYearButtonLabel: string;
previousMonthButtonLabel: string;
nextMonthButtonLabel: string;
yearItemNumber: number;
monthHeaderPosition: string;
};
constructor(props: CalendarProps);
componentDidMount(): void;
componentDidUpdate(prevProps: CalendarProps): void;
containerRef: React.RefObject<HTMLDivElement | null>;
monthContainer: CalendarState["monthContainer"];
assignMonthContainer: void | undefined;
handleClickOutside: (event: MouseEvent) => void;
setClickOutsideRef: () => HTMLDivElement | null;
handleDropdownFocus: (event: React.FocusEvent<HTMLDivElement>) => void;
getDateInView: () => Date;
increaseMonth: () => void;
decreaseMonth: () => void;
handleDayClick: (day: Date, event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>, monthSelectedIn?: number) => void;
handleDayMouseEnter: (day: Date) => void;
handleMonthMouseLeave: () => void;
handleYearMouseEnter: (event: React.MouseEvent<HTMLDivElement>, year: number) => void;
handleYearMouseLeave: (event: React.MouseEvent<HTMLDivElement>, year: number) => void;
handleYearChange: (date: Date) => void;
getEnabledPreSelectionDateForMonth: (date: Date) => Date | null;
handleMonthChange: (date: Date) => void;
handleCustomMonthChange: (date: Date) => void;
handleMonthYearChange: (date: Date) => void;
changeYear: (year: number) => void;
changeMonth: (month: number) => void;
changeMonthYear: (monthYear: Date) => void;
header: (date?: Date, customDayNameCount?: number) => React.ReactElement[];
formatWeekday: (day: Date, locale?: Locale) => string;
decreaseYear: () => void;
clearSelectingDate: () => void;
renderPreviousButton: () => React.ReactElement | void;
increaseYear: () => void;
renderNextButton: () => React.ReactElement | void;
renderCurrentMonth: (date?: Date) => React.ReactElement;
renderYearDropdown: (overrideHide?: boolean) => React.ReactElement | undefined;
renderMonthDropdown: (overrideHide?: boolean) => React.ReactElement | undefined;
renderMonthYearDropdown: (overrideHide?: boolean) => React.ReactElement | undefined;
handleTodayButtonClick: (event: React.MouseEvent<HTMLDivElement>) => void;
renderTodayButton: () => React.ReactElement | undefined;
renderDayNamesHeader: (monthDate: Date, customDayNameCount?: number) => React.JSX.Element;
renderDefaultHeader: ({ monthDate, i }: {
monthDate: Date;
i: number;
}) => React.JSX.Element;
renderCustomHeader: (headerArgs: {
monthDate: Date;
i: number;
}) => React.JSX.Element | null;
renderYearHeader: ({ monthDate, }: {
monthDate: Date;
}) => React.ReactElement;
renderHeader: ({ monthDate, i, }: {
monthDate: Date;
i?: number;
}) => React.ReactElement | null;
renderMonths: () => React.ReactElement[] | undefined;
renderYears: () => React.ReactElement | undefined;
renderTimeSection: () => React.ReactElement | undefined;
renderInputTimeSection: () => React.ReactElement | undefined;
renderAriaLiveRegion: () => React.ReactElement;
renderChildren: () => React.ReactElement | undefined;
render(): React.ReactElement;
}
export {};
+8
View File
@@ -0,0 +1,8 @@
import React, { type HTMLAttributes } from "react";
export interface CalendarContainerProps extends React.PropsWithChildren<HTMLAttributes<HTMLDivElement>> {
showTimeSelectOnly?: boolean;
showTime?: boolean;
inline?: boolean;
}
declare const CalendarContainer: React.FC<CalendarContainerProps>;
export default CalendarContainer;
+27
View File
@@ -0,0 +1,27 @@
import React from "react";
interface CalendarIconProps {
icon?: string | React.ReactNode;
className?: string;
onClick?: (event: React.MouseEvent) => void;
}
/**
* `CalendarIcon` is a React component that renders an icon for a calendar.
* The icon can be a string representing a CSS class, a React node, or a default SVG icon.
*
* @component
* @prop icon - The icon to be displayed. This can be a string representing a CSS class or a React node.
* @prop className - An optional string representing additional CSS classes to be applied to the icon.
* @prop onClick - An optional function to be called when the icon is clicked.
*
* @example
* // To use a CSS class as the icon
* <CalendarIcon icon="my-icon-class" onClick={myClickHandler} />
*
* @example
* // To use a React node as the icon
* <CalendarIcon icon={<MyIconComponent />} onClick={myClickHandler} />
*
* @returns The `CalendarIcon` component.
*/
declare const CalendarIcon: React.FC<CalendarIconProps>;
export default CalendarIcon;
+12
View File
@@ -0,0 +1,12 @@
import React from "react";
export type ClickOutsideHandler = (event: MouseEvent) => void;
interface ClickOutsideWrapperProps {
onClickOutside: ClickOutsideHandler;
className?: string;
children: React.ReactNode;
containerRef?: React.RefObject<HTMLDivElement | null>;
style?: React.CSSProperties;
ignoreClass?: string;
}
export declare const ClickOutsideWrapper: React.FC<ClickOutsideWrapperProps>;
export {};
+567
View File
@@ -0,0 +1,567 @@
import { addDays, addHours, addMinutes, addMonths, addQuarters, addSeconds, addWeeks, addYears, getDate, getDay, getHours, getMinutes, getMonth, getQuarter, getSeconds, getTime, getYear, isAfter, isBefore, isDate, set, setHours, setMinutes, setMonth, setQuarter, setYear, subDays, subMonths, subQuarters, subWeeks, subYears } from "date-fns";
import type { Locale as DateFnsLocale, Day } from "date-fns";
export type TimeZone = string;
/**
* Resets the date-fns-tz module cache. Used for testing.
* @internal
*/
export declare function __resetDateFnsTzCache(): void;
/**
* Sets the date-fns-tz module to null to simulate it not being installed. Used for testing.
* @internal
*/
export declare function __setDateFnsTzNull(): void;
/**
* Converts a date to the specified timezone.
* If no timezone is specified or date-fns-tz is not installed, returns the original date.
*
* @param date - The date to convert
* @param timeZone - The IANA timezone identifier (e.g., "America/New_York", "UTC")
* @returns The date in the specified timezone
*/
export declare function toZonedTime(date: Date, timeZone?: TimeZone): Date;
/**
* Converts a date from the specified timezone to UTC.
* If no timezone is specified or date-fns-tz is not installed, returns the original date.
*
* @param date - The date in the specified timezone
* @param timeZone - The IANA timezone identifier (e.g., "America/New_York", "UTC")
* @returns The date in UTC
*/
export declare function fromZonedTime(date: Date, timeZone?: TimeZone): Date;
/**
* Formats a date in the specified timezone.
* If no timezone is specified, uses the standard format function.
*
* @param date - The date to format
* @param formatStr - The format string
* @param timeZone - The IANA timezone identifier
* @param locale - The locale object
* @returns The formatted date string
*/
export declare function formatInTimeZone(date: Date, formatStr: string, timeZone?: TimeZone, locale?: DateFnsLocale): string;
/**
* Gets the current date/time in the specified timezone.
*
* @param timeZone - The IANA timezone identifier
* @returns The current date in the specified timezone
*/
export declare function nowInTimeZone(timeZone?: TimeZone): Date;
export type DateNumberType = Day;
interface LocaleObj extends Pick<DateFnsLocale, "options" | "formatLong" | "localize" | "match"> {
}
export type Locale = string | LocaleObj;
export declare enum KeyType {
ArrowUp = "ArrowUp",
ArrowDown = "ArrowDown",
ArrowLeft = "ArrowLeft",
ArrowRight = "ArrowRight",
PageUp = "PageUp",
PageDown = "PageDown",
Home = "Home",
End = "End",
Enter = "Enter",
Space = " ",
Tab = "Tab",
Escape = "Escape",
Backspace = "Backspace",
X = "x"
}
export declare const DEFAULT_YEAR_ITEM_NUMBER = 12;
export declare function newDate(value?: string | Date | number | null): Date;
/**
* Parses a date.
*
* @param value - The string representing the Date in a parsable form, e.g., ISO 1861
* @param dateFormat - The date format.
* @param locale - The locale.
* @param strictParsing - The strict parsing flag.
* @param refDate - The base date to be passed to date-fns parse() function.
* @returns - The parsed date or null.
*/
export declare function parseDate(value: string, dateFormat: string | string[], locale: Locale | undefined, strictParsing: boolean, refDate?: Date): Date | null;
/**
* Parses a partial date string for calendar navigation purposes.
* Unlike parseDate, this function attempts to extract whatever date
* information is available (year, month) from a partial input,
* returning a date suitable for navigating the calendar view.
*
* @param value - The date string to parse.
* @param refDate - The reference date to use for missing components.
* @returns - A date for navigation or null if no date info could be extracted.
*/
export declare function parseDateForNavigation(value: string, refDate?: Date): Date | null;
export { isDate, set };
/**
* Checks if a given date is a valid Date object.
* @param date - The date to be checked.
* @returns A boolean value indicating whether the date is valid.
*/
export declare function isValid(date: Date): boolean;
/**
* Safely returns a valid Date or null.
* This handles cases where a value might be passed as a string or other
* invalid type at runtime, even though TypeScript expects a Date.
* @param date - The value to check (typed as Date but could be anything at runtime)
* @returns The date if it's a valid Date object, otherwise null
*/
export declare function safeToDate(date: Date | null | undefined): Date | null;
/**
* Formats a date.
*
* @param date - The date.
* @param formatStr - The format string.
* @param locale - The locale.
* @returns - The formatted date.
*/
export declare function formatDate(date: Date, formatStr: string, locale?: Locale): string;
/**
* Safely formats a date.
*
* @param date - The date.
* @param options - An object containing the dateFormat and locale.
* @returns - The formatted date or an empty string.
*/
export declare function safeDateFormat(date: Date | null | undefined, { dateFormat, locale }: {
dateFormat: string | string[];
locale?: Locale;
}): string;
/**
* Used as a delimiter to separate two dates when formatting a date range
*/
export declare const DATE_RANGE_SEPARATOR = " - ";
/**
* Safely formats a date range.
*
* @param startDate - The start date.
* @param endDate - The end date.
* @param props - The props.
* @returns - The formatted date range or an empty string.
*/
export declare function safeDateRangeFormat(startDate: Date | null | undefined, endDate: Date | null | undefined, props: {
dateFormat: string | string[];
locale?: Locale;
rangeSeparator?: string;
}): string;
/**
* Safely formats multiple dates.
*
* @param dates - The dates.
* @param props - The props.
* @returns - The formatted dates or an empty string.
*/
export declare function safeMultipleDatesFormat(dates: Date[], props: {
dateFormat: string | string[];
locale?: Locale;
}): string;
/**
* Sets the time for a given date.
*
* @param date - The date.
* @param time - An object containing the hour, minute, and second.
* @returns - The date with the time set.
*/
export declare function setTime(date: Date, { hour, minute, second }: {
hour?: number | undefined;
minute?: number | undefined;
second?: number | undefined;
}): Date;
export { setHours, setMinutes, setMonth, setQuarter, setYear };
export { getDate, getDay, getHours, getMinutes, getMonth, getQuarter, getSeconds, getTime, getYear, };
/**
* Gets the week of the year for a given date.
*
* @param date - The date.
* @returns - The week of the year.
*/
export declare function getWeek(date: Date): number;
/**
* Gets the day of the week code for a given day.
*
* @param day - The day.
* @param locale - The locale.
* @returns - The day of the week code.
*/
export declare function getDayOfWeekCode(day: Date, locale?: Locale): string;
/**
* Gets the start of the day for a given date.
*
* @param date - The date.
* @returns - The start of the day.
*/
export declare function getStartOfDay(date: Date): Date;
/**
* Gets the start of the week for a given date.
*
* @param date - The date.
* @param locale - The locale.
* @param calendarStartDay - The day the calendar starts on.
* @returns - The start of the week.
*/
export declare function getStartOfWeek(date: Date, locale?: Locale, calendarStartDay?: Day): Date;
/**
* Gets the start of the month for a given date.
*
* @param date - The date.
* @returns - The start of the month.
*/
export declare function getStartOfMonth(date: Date): Date;
/**
* Gets the start of the year for a given date.
*
* @param date - The date.
* @returns - The start of the year.
*/
export declare function getStartOfYear(date: Date): Date;
/**
* Gets the start of the quarter for a given date.
*
* @param date - The date.
* @returns - The start of the quarter.
*/
export declare function getStartOfQuarter(date: Date): Date;
/**
* Gets the start of today.
*
* @returns - The start of today.
*/
export declare function getStartOfToday(): Date;
/**
* Gets the end of the day for a given date.
*
* @param date - The date.
* @returns - The end of the day.
*/
export declare function getEndOfDay(date: Date): Date;
/**
* Gets the end of the week for a given date.
*
* @param date - The date.
* @returns - The end of the week.
*/
export declare function getEndOfWeek(date: Date): Date;
/**
* Gets the end of the month for a given date.
*
* @param date - The date.
* @returns - The end of the month.
*/
export declare function getEndOfMonth(date: Date): Date;
export { addDays, addMinutes, addMonths, addQuarters, addSeconds, addWeeks, addYears, };
export { addHours, subDays, subMonths, subQuarters, subWeeks, subYears };
export { isAfter, isBefore };
/**
* Checks if two dates are in the same year.
*
* @param date1 - The first date.
* @param date2 - The second date.
* @returns - True if the dates are in the same year, false otherwise.
*/
export declare function isSameYear(date1: Date | null, date2: Date | null): boolean;
/**
* Checks if two dates are in the same month.
*
* @param date1 - The first date.
* @param date2 - The second date.
* @returns - True if the dates are in the same month, false otherwise.
*/
export declare function isSameMonth(date1: Date | null, date2?: Date | null): boolean;
/**
* Checks if two dates are in the same quarter.
*
* @param date1 - The first date.
* @param date2 - The second date.
* @returns - True if the dates are in the same quarter, false otherwise.
*/
export declare function isSameQuarter(date1: Date | null, date2: Date | null): boolean;
/**
* Checks if two dates are on the same day.
*
* @param date1 - The first date.
* @param date2 - The second date.
* @returns - True if the dates are on the same day, false otherwise.
*/
export declare function isSameDay(date1?: Date | null, date2?: Date | null): boolean;
/**
* Checks if two dates are equal.
*
* @param date1 - The first date.
* @param date2 - The second date.
* @returns - True if the dates are equal, false otherwise.
*/
export declare function isEqual(date1: Date | null | undefined, date2: Date | null | undefined): boolean;
/**
* Checks if a day is within a date range.
*
* @param day - The day to check.
* @param startDate - The start date of the range.
* @param endDate - The end date of the range.
* @returns - True if the day is within the range, false otherwise.
*/
export declare function isDayInRange(day: Date, startDate: Date, endDate: Date): boolean;
/**
* Gets the difference in days between two dates.
*
* @param date1 - The first date.
* @param date2 - The second date.
* @returns - The difference in days.
*/
export declare function getDaysDiff(date1: Date, date2: Date): number;
/**
* Registers a locale.
*
* @param localeName - The name of the locale.
* @param localeData - The data of the locale.
*/
export declare function registerLocale(localeName: string, localeData: LocaleObj): void;
/**
* Sets the default locale.
*
* @param localeName - The name of the locale.
*/
export declare function setDefaultLocale(localeName?: string): void;
/**
* Gets the default locale.
*
* @returns - The default locale.
*/
export declare function getDefaultLocale(): string | undefined;
/**
* Gets the locale object.
*
* @param localeSpec - The locale specification.
* @returns - The locale object.
*/
export declare function getLocaleObject(localeSpec?: Locale): LocaleObj | undefined;
/**
* Formats the weekday in a given locale.
*
* @param date - The date to format.
* @param formatFunc - The formatting function.
* @param locale - The locale to use for formatting.
* @returns - The formatted weekday.
*/
export declare function getFormattedWeekdayInLocale(date: Date, formatFunc: (date: string) => string, locale?: Locale): string;
/**
* Gets the minimum weekday in a given locale.
*
* @param date - The date to format.
* @param locale - The locale to use for formatting.
* @returns - The minimum weekday.
*/
export declare function getWeekdayMinInLocale(date: Date, locale?: Locale): string;
/**
* Gets the short weekday in a given locale.
*
* @param date - The date to format.
* @param locale - The locale to use for formatting.
* @returns - The short weekday.
*/
export declare function getWeekdayShortInLocale(date: Date, locale?: Locale): string;
/**
* Gets the month in a given locale.
*
* @param month - The month to format.
* @param locale - The locale to use for formatting.
* @returns - The month.
*/
export declare function getMonthInLocale(month: number, locale?: Locale): string;
/**
* Gets the short month in a given locale.
*
* @param month - The month to format.
* @param locale - The locale to use for formatting.
* @returns - The short month.
*/
export declare function getMonthShortInLocale(month: number, locale?: Locale): string;
/**
* Gets the short quarter in a given locale.
*
* @param quarter - The quarter to format.
* @param locale - The locale to use for formatting.
* @returns - The short quarter.
*/
export declare function getQuarterShortInLocale(quarter: number, locale?: Locale): string;
export interface DateFilterOptions {
minDate?: Date;
maxDate?: Date;
excludeDates?: {
date: Date;
message?: string;
}[] | Date[];
excludeDateIntervals?: {
start: Date;
end: Date;
}[];
includeDates?: Date[];
includeDateIntervals?: {
start: Date;
end: Date;
}[];
filterDate?: (date: Date) => boolean;
yearItemNumber?: number;
}
export type DateFilterOptionsWithDisabled = DateFilterOptions & {
disabled?: boolean;
};
/**
* Checks if a day is disabled.
*
* @param day - The day to check.
* @param options - The options to consider when checking.
* @returns - Returns true if the day is disabled, false otherwise.
*/
export declare function isDayDisabled(day: Date, { minDate, maxDate, excludeDates, excludeDateIntervals, includeDates, includeDateIntervals, filterDate, disabled, }?: DateFilterOptionsWithDisabled): boolean;
/**
* Checks if a day is excluded.
*
* @param day - The day to check.
* @param options - The options to consider when checking.
* @returns - Returns true if the day is excluded, false otherwise.
*/
export declare function isDayExcluded(day: Date, { excludeDates, excludeDateIntervals, }?: Pick<DateFilterOptions, "excludeDates" | "excludeDateIntervals">): boolean;
export declare function isMonthDisabled(month: Date, { minDate, maxDate, excludeDates, includeDates, filterDate, }?: Pick<DateFilterOptions, "minDate" | "maxDate" | "excludeDates" | "includeDates" | "filterDate">): boolean;
export declare function isMonthInRange(startDate: Date, endDate: Date, m: number, day: Date): boolean;
/**
* To check if a date's month and year are disabled/excluded
* @param date Date to check
* @returns {boolean} true if month and year are disabled/excluded, false otherwise
*/
export declare function isMonthYearDisabled(date: Date, { minDate, maxDate, excludeDates, includeDates, }?: Pick<DateFilterOptions, "minDate" | "maxDate" | "excludeDates" | "includeDates">): boolean;
export declare function isQuarterDisabled(quarter: Date, { minDate, maxDate, excludeDates, includeDates, filterDate, disabled, }?: Pick<DateFilterOptionsWithDisabled, "minDate" | "maxDate" | "excludeDates" | "includeDates" | "filterDate" | "disabled">): boolean;
export declare function isYearInRange(year: number, start?: Date | null, end?: Date | null): boolean;
export declare function isYearDisabled(year: number, { minDate, maxDate, excludeDates, includeDates, filterDate, disabled, }?: Pick<DateFilterOptionsWithDisabled, "minDate" | "maxDate" | "excludeDates" | "includeDates" | "filterDate" | "disabled">): boolean;
export declare function isQuarterInRange(startDate: Date, endDate: Date, q: number, day: Date): boolean;
export declare function isOutOfBounds(day: Date, { minDate, maxDate }?: Pick<DateFilterOptions, "minDate" | "maxDate">): boolean;
export declare function isTimeInList(time: Date, times: Date[]): boolean;
export interface TimeFilterOptions {
minTime?: Date;
maxTime?: Date;
excludeTimes?: Date[];
includeTimes?: Date[];
filterTime?: (time: Date) => boolean;
}
export declare function isTimeDisabled(time: Date, { excludeTimes, includeTimes, filterTime, }?: Pick<TimeFilterOptions, "excludeTimes" | "includeTimes" | "filterTime">): boolean;
export declare function isTimeInDisabledRange(time: Date, { minTime, maxTime }: Pick<TimeFilterOptions, "minTime" | "maxTime">): boolean;
export declare function monthDisabledBefore(day: Date, { minDate, includeDates, }?: Pick<DateFilterOptions, "minDate" | "includeDates">): boolean;
export declare function monthDisabledAfter(day: Date, { maxDate, includeDates, }?: Pick<DateFilterOptions, "maxDate" | "includeDates">): boolean;
export declare function quarterDisabledBefore(date: Date, { minDate, includeDates, }?: Pick<DateFilterOptions, "minDate" | "includeDates">): boolean;
export declare function quarterDisabledAfter(date: Date, { maxDate, includeDates, }?: Pick<DateFilterOptions, "maxDate" | "includeDates">): boolean;
export declare function yearDisabledBefore(day: Date, { minDate, includeDates, }?: Pick<DateFilterOptions, "minDate" | "includeDates">): boolean;
export declare function yearsDisabledBefore(day: Date, { minDate, yearItemNumber, }?: Pick<DateFilterOptions, "minDate" | "yearItemNumber">): boolean;
export declare function yearDisabledAfter(day: Date, { maxDate, includeDates, }?: Pick<DateFilterOptions, "maxDate" | "includeDates">): boolean;
export declare function yearsDisabledAfter(day: Date, { maxDate, yearItemNumber, }?: Pick<DateFilterOptions, "maxDate" | "yearItemNumber">): boolean;
export declare function getEffectiveMinDate({ minDate, includeDates, }: Pick<DateFilterOptions, "minDate" | "includeDates">): Date | undefined;
export declare function getEffectiveMaxDate({ maxDate, includeDates, }: Pick<DateFilterOptions, "maxDate" | "includeDates">): Date | undefined;
export interface HighlightDate {
[className: string]: Date[];
}
/**
* Get a map of highlighted dates with their corresponding classes.
* @param highlightDates The dates to highlight.
* @param defaultClassName The default class to use for highlighting.
* @returns A map with dates as keys and arrays of class names as values.
*/
export declare function getHighLightDaysMap(highlightDates?: (Date | HighlightDate)[], defaultClassName?: string): Map<string, string[]>;
/**
* Compare the two arrays
* @param array1 The first array to compare.
* @param array2 The second array to compare.
* @returns true, if the passed arrays are equal, false otherwise.
*/
export declare function arraysAreEqual<T>(array1: T[], array2: T[]): boolean;
export interface HolidayItem {
date: Date;
holidayName: string;
}
interface ClassNamesObj {
className: string;
holidayNames: string[];
}
export type HolidaysMap = Map<string, ClassNamesObj>;
/**
* Assign the custom class to each date
* @param holidayDates array of object containing date and name of the holiday
* @param defaultClassName className to be added.
* @returns Map containing date as key and array of className and holiday name as value
*/
export declare function getHolidaysMap(holidayDates?: HolidayItem[], defaultClassName?: string): HolidaysMap;
/**
* Determines the times to inject after a given start of day, current time, and multiplier.
* @param startOfDay The start of the day.
* @param currentTime The current time.
* @param currentMultiplier The current multiplier.
* @param intervals The intervals.
* @param injectedTimes The times to potentially inject.
* @returns An array of times to inject.
*/
export declare function timesToInjectAfter(startOfDay: Date, currentTime: Date, currentMultiplier: number, intervals: number, injectedTimes: Date[]): Date[];
/**
* Adds a leading zero to a number if it's less than 10.
* @param i The number to add a leading zero to.
* @returns The number as a string, with a leading zero if it was less than 10.
*/
export declare function addZero(i: number): string;
/**
* Gets the start and end years for a period.
* @param date The date to get the period for.
* @param yearItemNumber The number of years in the period. Defaults to DEFAULT_YEAR_ITEM_NUMBER.
* @returns An object with the start and end years for the period.
*/
export declare function getYearsPeriod(date: Date, yearItemNumber?: number): {
startPeriod: number;
endPeriod: number;
};
/**
* Gets the number of hours in a day.
* @param d The date to get the number of hours for.
* @returns The number of hours in the day.
*/
export declare function getHoursInDay(d: Date): number;
/**
* Returns the start of the minute for the given date
*
* NOTE: this function is a DST and timezone-safe analog of `date-fns/startOfMinute`
* do not make changes unless you know what you're doing
*
* See comments on https://github.com/Hacker0x01/react-datepicker/pull/4244
* for more details
*
* @param d date
* @returns start of the minute
*/
export declare function startOfMinute(d: Date): Date;
/**
* Returns whether the given dates are in the same minute
*
* This function is a DST and timezone-safe analog of `date-fns/isSameMinute`
*
* @param d1
* @param d2
* @returns
*/
export declare function isSameMinute(d1: Date, d2: Date): boolean;
/**
* Returns a new datetime object representing the input date with midnight time
* @param date The date to get the midnight time for
* @returns A new datetime object representing the input date with midnight time
*/
export declare function getMidnightDate(date: Date): Date;
/**
* Is the first date before the second one?
* @param date The date that should be before the other one to return true
* @param dateToCompare The date to compare with
* @returns The first date is before the second date
*
* Note:
* This function considers the mid-night of the given dates for comparison.
* It evaluates whether date is before dateToCompare based on their mid-night timestamps.
*/
export declare function isDateBefore(date: Date, dateToCompare: Date): boolean;
/**
* Checks if the space key was pressed down.
*
* @param event - The keyboard event.
* @returns - Returns true if the space key was pressed down, false otherwise.
*/
export declare function isSpaceKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean;
+151
View File
@@ -0,0 +1,151 @@
import React, { Component } from "react";
import { type DateFilterOptionsWithDisabled, type DateNumberType, type Locale, type HolidaysMap } from "./date_utils";
interface DayProps extends Pick<DateFilterOptionsWithDisabled, "minDate" | "maxDate" | "excludeDates" | "excludeDateIntervals" | "includeDateIntervals" | "includeDates" | "filterDate" | "disabled"> {
ariaLabelPrefixWhenEnabled?: string;
ariaLabelPrefixWhenDisabled?: string;
disabledKeyboardNavigation?: boolean;
day: Date;
dayClassName?: (date: Date) => string;
highlightDates?: Map<string, string[]>;
holidays?: HolidaysMap;
inline?: boolean;
shouldFocusDayInline?: boolean;
month: number;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
handleOnKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
usePointerEvent?: boolean;
preSelection?: Date | null;
selected?: Date | null;
selectingDate?: Date;
selectsEnd?: boolean;
selectsStart?: boolean;
selectsRange?: boolean;
showWeekPicker?: boolean;
showWeekNumber?: boolean;
selectsDisabledDaysInRange?: boolean;
selectsMultiple?: boolean;
selectedDates?: Date[];
startDate?: Date | null;
endDate?: Date | null;
renderDayContents?: (day: number, date: Date) => React.ReactNode;
containerRef?: React.RefObject<HTMLDivElement | null>;
calendarStartDay?: DateNumberType;
locale?: Locale;
monthShowsDuplicateDaysEnd?: boolean;
monthShowsDuplicateDaysStart?: boolean;
swapRange?: boolean;
}
/**
* `Day` is a React component that represents a single day in a date picker.
* It handles the rendering and interaction of a day.
*
* @prop ariaLabelPrefixWhenEnabled - Aria label prefix when the day is enabled.
* @prop ariaLabelPrefixWhenDisabled - Aria label prefix when the day is disabled.
* @prop disabledKeyboardNavigation - Whether keyboard navigation is disabled.
* @prop day - The day to be displayed.
* @prop dayClassName - Function to customize the CSS class of the day.
* @prop endDate - The end date in a range.
* @prop highlightDates - Map of dates to be highlighted.
* @prop holidays - Map of holiday dates.
* @prop inline - Whether the date picker is inline.
* @prop shouldFocusDayInline - Whether the day should be focused when date picker is inline.
* @prop month - The month the day belongs to.
* @prop onClick - Click event handler.
* @prop onMouseEnter - Mouse enter event handler.
* @prop handleOnKeyDown - Key down event handler.
* @prop usePointerEvent - Whether to use pointer events.
* @prop preSelection - The date that is currently selected.
* @prop selected - The selected date.
* @prop selectingDate - The date currently being selected.
* @prop selectsEnd - Whether the day can be the end date in a range.
* @prop selectsStart - Whether the day can be the start date in a range.
* @prop selectsRange - Whether the day can be in a range.
* @prop showWeekPicker - Whether to show week picker.
* @prop showWeekNumber - Whether to show week numbers.
* @prop selectsDisabledDaysInRange - Whether to select disabled days in a range.
* @prop selectsMultiple - Whether to allow multiple date selection.
* @prop selectedDates - Array of selected dates.
* @prop startDate - The start date in a range.
* @prop renderDayContents - Function to customize the rendering of the day's contents.
* @prop containerRef - Ref for the container.
* @prop excludeDates - Array of dates to be excluded.
* @prop calendarStartDay - The start day of the week.
* @prop locale - The locale object.
* @prop monthShowsDuplicateDaysEnd - Whether to show duplicate days at the end of the month.
* @prop monthShowsDuplicateDaysStart - Whether to show duplicate days at the start of the month.
* @prop includeDates - Array of dates to be included.
* @prop includeDateIntervals - Array of date intervals to be included.
* @prop minDate - The minimum date that can be selected.
* @prop maxDate - The maximum date that can be selected.
*
* @example
* ```tsx
* import React from 'react';
* import Day from './day';
*
* function MyComponent() {
* const handleDayClick = (event) => {
* console.log('Day clicked', event);
* };
*
* const handleDayMouseEnter = (event) => {
* console.log('Mouse entered day', event);
* };
*
* const renderDayContents = (date) => {
* return <div>{date.getDate()}</div>;
* };
*
* return (
* <Day
* day={new Date()}
* onClick={handleDayClick}
* onMouseEnter={handleDayMouseEnter}
* renderDayContents={renderDayContents}
* />
* );
* }
*
* export default MyComponent;
* ```
*/
export default class Day extends Component<DayProps> {
componentDidMount(): void;
componentDidUpdate(): void;
dayEl: React.RefObject<HTMLDivElement | null>;
handleClick: DayProps["onClick"];
handleMouseEnter: DayProps["onMouseEnter"];
handleOnKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
isSameDay: (other: Date | null | undefined) => boolean;
isKeyboardSelected: () => boolean | undefined;
isDisabled: (day?: Date) => boolean;
isExcluded: () => boolean;
isStartOfWeek: () => boolean;
isSameWeek: (other?: Date | null) => boolean | undefined;
isSameDayOrWeek: (other?: Date | null) => boolean | undefined;
getHighLightedClass: () => false | string[] | undefined;
getHolidaysClass: () => (string | undefined)[];
isInRange: () => boolean;
isInSelectingRange: () => boolean;
isSelectingRangeStart: () => boolean;
isSelectingRangeEnd: () => boolean;
isRangeStart: () => boolean;
isRangeEnd: () => boolean;
isWeekend: () => boolean;
isAfterMonth: () => boolean;
isBeforeMonth: () => boolean;
isCurrentDay: () => boolean;
isSelected: () => boolean | undefined;
getClassNames: (date: Date) => string;
getAriaLabel: () => string;
getTitle: () => string;
getTabIndex: () => 0 | -1;
handleFocusDay: () => void;
private shouldFocusDay;
private isDayActiveElement;
private isDuplicateDay;
renderDayContents: () => React.ReactNode;
render: () => React.JSX.Element;
}
export {};
+252
View File
@@ -0,0 +1,252 @@
import React, { Component, cloneElement } from "react";
import Calendar from "./calendar";
import CalendarIcon from "./calendar_icon";
import { registerLocale, setDefaultLocale, getDefaultLocale, type HighlightDate, type HolidayItem, type TimeZone } from "./date_utils";
import PopperComponent from "./popper_component";
import Portal from "./portal";
import type { ClickOutsideHandler } from "./click_outside_wrapper";
export { default as CalendarContainer } from "./calendar_container";
export { registerLocale, setDefaultLocale, getDefaultLocale };
export { ReactDatePickerCustomHeaderProps, ReactDatePickerCustomDayNameProps, } from "./calendar";
interface Holiday {
date: string;
holidayName: string;
}
type CalendarProps = React.ComponentPropsWithoutRef<typeof Calendar>;
interface CalendarIconProps extends React.ComponentPropsWithoutRef<typeof CalendarIcon> {
}
interface PortalProps extends React.ComponentPropsWithoutRef<typeof Portal> {
}
interface PopperComponentProps extends React.ComponentPropsWithoutRef<typeof PopperComponent> {
}
type OmitUnion<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
export type DatePickerProps = OmitUnion<CalendarProps, "setOpen" | "dateFormat" | "preSelection" | "onSelect" | "onClickOutside" | "highlightDates" | "holidays" | "shouldFocusDayInline" | "monthSelectedIn" | "onDropdownFocus" | "onTimeChange" | "className" | "container" | "handleOnKeyDown" | "handleOnDayKeyDown" | "isInputFocused" | "setPreSelection" | "selectsRange" | "selectsMultiple" | "dropdownMode"> & Partial<Pick<CalendarIconProps, "icon">> & OmitUnion<PortalProps, "children" | "portalId"> & OmitUnion<PopperComponentProps, "className" | "hidePopper" | "targetComponent" | "popperComponent" | "popperOnKeyDown" | "showArrow"> & {
dateFormatCalendar?: CalendarProps["dateFormat"];
calendarClassName?: CalendarProps["className"];
calendarContainer?: CalendarProps["container"];
dropdownMode?: CalendarProps["dropdownMode"];
onKeyDown?: (event: React.KeyboardEvent<HTMLElement>) => void;
popperClassName?: PopperComponentProps["className"];
showPopperArrow?: PopperComponentProps["showArrow"];
popperTargetRef?: React.RefObject<HTMLElement | null>;
open?: boolean;
disabled?: boolean;
readOnly?: boolean;
startOpen?: boolean;
onFocus?: React.FocusEventHandler<HTMLElement>;
onBlur?: React.FocusEventHandler<HTMLElement>;
onClickOutside?: ClickOutsideHandler;
onInputClick?: VoidFunction;
preventOpenOnFocus?: boolean;
closeOnScroll?: boolean | ((event: Event) => boolean);
isClearable?: boolean;
clearButtonTitle?: string;
clearButtonClassName?: string;
ariaLabelClose?: string;
className?: string;
customInput?: Parameters<typeof cloneElement>[0];
dateFormat?: string | string[];
showDateSelect?: boolean;
highlightDates?: (Date | HighlightDate)[];
onCalendarOpen?: VoidFunction;
onCalendarClose?: VoidFunction;
strictParsing?: boolean;
swapRange?: boolean;
onInputError?: (error: {
code: 1;
msg: string;
}) => void;
allowSameDay?: boolean;
withPortal?: boolean;
focusSelectedMonth?: boolean;
showIcon?: boolean;
calendarIconClassname?: never;
calendarIconClassName?: string;
toggleCalendarOnIconClick?: boolean;
holidays?: Holiday[];
startDate?: Date | null;
endDate?: Date | null;
selected?: Date | null;
/**
* The IANA timezone identifier (e.g., "America/New_York", "UTC", "Europe/London").
* When set, the datepicker will display dates/times in this timezone and
* the onChange callback will return dates adjusted to this timezone.
*
* Requires the optional peer dependency `date-fns-tz` to be installed:
* ```
* npm install date-fns-tz
* ```
*
* @example
* ```tsx
* <DatePicker
* timeZone="America/New_York"
* selected={selectedDate}
* onChange={(date) => setSelectedDate(date)}
* />
* ```
*/
timeZone?: TimeZone;
value?: string;
customInputRef?: string;
id?: string;
name?: string;
form?: string;
autoFocus?: boolean;
placeholderText?: string;
autoComplete?: string;
title?: string;
required?: boolean;
tabIndex?: number;
ariaDescribedBy?: string;
ariaInvalid?: string;
ariaLabel?: string;
ariaLabelledBy?: string;
ariaRequired?: string;
"aria-describedby"?: string;
"aria-invalid"?: string;
"aria-label"?: string;
"aria-labelledby"?: string;
"aria-required"?: string;
rangeSeparator?: string;
onChangeRaw?: (event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, selectionMeta?: {
date: Date;
formattedDate: string;
}) => void;
onSelect?: (date: Date | null, event?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => void;
} & ({
selectsRange?: false | undefined;
selectsMultiple?: false | undefined;
formatMultipleDates?: never;
onChange?: (date: Date | null, event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
} | {
selectsRange?: true;
selectsMultiple?: false | undefined;
formatMultipleDates?: never;
onChange?: (date: [Date | null, Date | null], event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
} | {
selectsRange?: false | undefined;
selectsMultiple?: true;
formatMultipleDates?: (dates: Date[], formatDate: (date: Date) => string) => string;
onChange?: (dates: Date[] | null, event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void;
});
interface DatePickerState {
open: boolean;
wasHidden: boolean;
lastPreSelectChange?: typeof PRESELECT_CHANGE_VIA_INPUT | typeof PRESELECT_CHANGE_VIA_NAVIGATE;
inputValue: string | null;
preventFocus: boolean;
preSelection?: CalendarProps["preSelection"];
shouldFocusDayInline?: CalendarProps["shouldFocusDayInline"];
monthSelectedIn?: CalendarProps["monthSelectedIn"];
focused?: CalendarProps["isInputFocused"];
highlightDates: Required<CalendarProps>["highlightDates"];
isRenderAriaLiveMessage?: boolean;
}
export declare class DatePicker extends Component<DatePickerProps, DatePickerState> {
static get defaultProps(): {
allowSameDay: boolean;
dateFormat: string;
dateFormatCalendar: string;
disabled: boolean;
disabledKeyboardNavigation: boolean;
dropdownMode: "scroll";
preventOpenOnFocus: boolean;
monthsShown: number;
outsideClickIgnoreClass: string;
readOnly: boolean;
rangeSeparator: string;
withPortal: boolean;
selectsDisabledDaysInRange: boolean;
shouldCloseOnSelect: boolean;
showTimeSelect: boolean;
showTimeInput: boolean;
showPreviousMonths: boolean;
showMonthYearPicker: boolean;
showFullMonthYearPicker: boolean;
showTwoColumnMonthYearPicker: boolean;
showFourColumnMonthYearPicker: boolean;
showYearPicker: boolean;
showQuarterYearPicker: boolean;
showWeekPicker: boolean;
strictParsing: boolean;
swapRange: boolean;
timeIntervals: number;
timeCaption: string;
previousMonthAriaLabel: string;
previousMonthButtonLabel: string;
nextMonthAriaLabel: string;
nextMonthButtonLabel: string;
previousYearAriaLabel: string;
previousYearButtonLabel: string;
nextYearAriaLabel: string;
nextYearButtonLabel: string;
timeInputLabel: string;
enableTabLoop: boolean;
yearItemNumber: number;
focusSelectedMonth: boolean;
showPopperArrow: boolean;
excludeScrollbar: boolean;
customTimeInput: null;
calendarStartDay: undefined;
toggleCalendarOnIconClick: boolean;
usePointerEvent: boolean;
};
constructor(props: DatePickerProps);
componentDidMount(): void;
componentDidUpdate(prevProps: DatePickerProps, prevState: DatePickerState): void;
componentWillUnmount(): void;
preventFocusTimeout: ReturnType<typeof setTimeout> | undefined;
inputFocusTimeout: ReturnType<typeof setTimeout> | undefined;
calendar: Calendar | null;
input: HTMLElement | null;
getPreSelection: () => Date;
modifyHolidays: () => HolidayItem[] | undefined;
calcInitialState: () => DatePickerState;
getInputValue: () => string;
resetHiddenStatus: () => void;
setHiddenStatus: () => void;
setHiddenStateOnVisibilityHidden: () => void;
clearPreventFocusTimeout: () => void;
setFocus: () => void;
setBlur: () => void;
deferBlur: () => void;
setOpen: (open: boolean, skipSetBlur?: boolean) => void;
inputOk: () => boolean;
isCalendarOpen: () => boolean;
handleFocus: (event: React.FocusEvent<HTMLElement>) => void;
sendFocusBackToInput: () => void;
cancelFocusInput: () => void;
deferFocusInput: () => void;
handleDropdownFocus: () => void;
resetInputValue: () => void;
handleBlur: (event: React.FocusEvent<HTMLElement>) => void;
handleCalendarClickOutside: (event: MouseEvent) => void;
handleChange: (...allArgs: Parameters<Required<DatePickerProps>["onChangeRaw"]>) => void;
handleSelect: (date: Date, event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, monthSelectedIn?: number) => void;
setSelected: (date: Date | null, event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, keepInput?: boolean, monthSelectedIn?: number) => void;
setPreSelection: (date?: Date | null) => void;
toggleCalendar: () => void;
handleTimeChange: (time: Date, modifyDateType?: "start" | "end") => void;
onInputClick: () => void;
handleTimeOnlyArrowKey: (eventKey: string) => void;
handleTimeOnlyEnterKey: (event: React.KeyboardEvent<HTMLElement>) => void;
scrollToTimeOption: (time: Date) => void;
onInputKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
onPortalKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
onDayKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
onPopperKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
onClearClick: (event?: React.MouseEvent<HTMLButtonElement>) => void;
clear: () => void;
onScroll: (event: Event) => void;
handleMonthSelectedInChange: (monthSelectedIn: number) => void;
renderCalendar: () => React.JSX.Element | null;
renderAriaLiveRegion: () => React.JSX.Element;
renderDateInput: () => React.FunctionComponentElement<any>;
renderClearButton: () => React.ReactElement | null;
renderInputContainer(): React.ReactElement;
render(): React.ReactElement | null;
}
declare const PRESELECT_CHANGE_VIA_INPUT = "input";
declare const PRESELECT_CHANGE_VIA_NAVIGATE = "navigate";
export default DatePicker;
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+42
View File
@@ -0,0 +1,42 @@
import React, { Component } from "react";
interface InputTimeProps {
onChange?: (date: Date) => void;
date?: Date;
timeString?: string;
timeInputLabel?: string;
customTimeInput?: React.ReactElement<{
date?: Date;
value: string;
onChange: (time: string) => void;
}>;
}
interface InputTimeState {
time?: string;
}
/**
* `InputTime` is a React component that manages time input.
*
* @component
* @example
* <InputTime timeString="12:00" />
*
* @param props - The properties that define the `InputTime` component.
* @param props.onChange - Function that is called when the date changes.
* @param props.date - The initial date value.
* @param props.timeString - The initial time string value.
* @param props.timeInputLabel - The label for the time input.
* @param props.customTimeInput - An optional custom time input element.
*
* @returns The `InputTime` component.
*/
export default class InputTime extends Component<InputTimeProps, InputTimeState> {
inputRef: React.RefObject<HTMLInputElement | null>;
constructor(props: InputTimeProps);
static getDerivedStateFromProps(props: InputTimeProps, state: InputTimeState): {
time: string | undefined;
} | null;
onTimeChange: (time: InputTimeState["time"]) => void;
renderTimeInput: () => React.JSX.Element;
render(): React.JSX.Element;
}
export {};
+183
View File
@@ -0,0 +1,183 @@
import React, { Component } from "react";
import { KeyType } from "./date_utils";
import Week from "./week";
interface WeekProps extends React.ComponentPropsWithoutRef<typeof Week> {
}
interface MonthProps extends Omit<WeekProps, "ariaLabelPrefix" | "chooseDayAriaLabelPrefix" | "day" | "disabledDayAriaLabelPrefix" | "month" | "onDayClick" | "onDayMouseEnter" | "preSelection" | "selected" | "showWeekNumber"> {
monthClassName?: (date: Date) => string;
onDayClick?: (date: Date, event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>, orderInDisplay?: number) => void;
onDayMouseEnter?: (date: Date) => void;
onMouseLeave?: VoidFunction;
setPreSelection?: (date?: Date | null) => void;
renderMonthContent?: (m: number, shortMonthText: string, fullMonthText: string, day: Date) => React.ReactNode;
renderQuarterContent?: (q: number, shortQuarter: string) => React.ReactNode;
handleOnMonthKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
ariaLabelPrefix?: string;
day: Date;
startDate?: Date | null;
endDate?: Date | null;
orderInDisplay?: number;
fixedHeight?: boolean;
peekNextMonth?: boolean;
preSelection?: Date | null;
selected?: Date | null;
showWeekNumbers?: WeekProps["showWeekNumber"];
showMonthYearPicker?: boolean;
showFullMonthYearPicker?: boolean;
showTwoColumnMonthYearPicker?: boolean;
showFourColumnMonthYearPicker?: boolean;
showQuarterYearPicker?: boolean;
weekAriaLabelPrefix?: WeekProps["ariaLabelPrefix"];
chooseDayAriaLabelPrefix?: WeekProps["chooseDayAriaLabelPrefix"];
disabledDayAriaLabelPrefix?: WeekProps["disabledDayAriaLabelPrefix"];
dayNamesHeader?: React.ReactNode;
monthHeader?: React.ReactNode;
monthFooter?: React.ReactNode;
}
/**
* `Month` is a React component that represents a month in a calendar.
* It accepts a `MonthProps` object as props which provides various configurations and event handlers.
*
* @prop dayClassName - Function to determine the class name for a day.
* @prop monthClassName - Function to determine the class name for a month.
* @prop filterDate - Function to filter dates.
* @prop formatWeekNumber - Function to format the week number.
* @prop onDayClick - Function to handle day click events.
* @prop onDayMouseEnter - Function to handle mouse enter events on a day.
* @prop onMouseLeave - Function to handle mouse leave events.
* @prop onWeekSelect - Function to handle week selection.
* @prop setPreSelection - Function to set pre-selection.
* @prop setOpen - Function to set open state.
* @prop renderDayContents - Function to render day contents.
* @prop renderMonthContent - Function to render month content.
* @prop renderQuarterContent - Function to render quarter content.
* @prop handleOnKeyDown - Function to handle key down events.
* @prop handleOnMonthKeyDown - Function to handle key down events on a month.
* @prop ariaLabelPrefix - Aria label prefix.
* @prop chooseDayAriaLabelPrefix - Aria label prefix for choosing a day.
* @prop disabledDayAriaLabelPrefix - Aria label prefix for disabled day.
* @prop disabledKeyboardNavigation - Flag to disable keyboard navigation.
* @prop day - The day.
* @prop endDate - The end date.
* @prop orderInDisplay - The order in display.
* @prop excludeDates - Dates to exclude.
* @prop excludeDateIntervals - Date intervals to exclude.
* @prop fixedHeight - Flag to set fixed height.
* @prop highlightDates - Dates to highlight.
* @prop holidays - Holidays.
* @prop includeDates - Dates to include.
* @prop includeDateIntervals - Date intervals to include.
* @prop inline - Flag to set inline.
* @prop shouldFocusDayInline - Flag to set focus on day inline.
* @prop locale - The locale.
* @prop maxDate - The maximum date.
* @prop minDate - The minimum date.
* @prop usePointerEvent - Flag to use pointer event.
* @prop peekNextMonth - Flag to peek next month.
* @prop preSelection - The pre-selection.
* @prop selected - The selected date.
* @prop selectingDate - The selecting date.
* @prop calendarStartDay - The calendar start day.
* @prop selectsEnd - Flag to select end.
* @prop selectsStart - Flag to select start.
* @prop selectsRange - Flag to select range.
* @prop selectsDisabledDaysInRange - Flag to select disabled days in range.
* @prop selectsMultiple - Flag to select multiple.
* @prop selectedDates - The selected dates.
* @prop showWeekNumbers - Flag to show week numbers.
* @prop startDate - The start date.
* @prop shouldCloseOnSelect - Flag to close on select.
* @prop showMonthYearPicker - Flag to show month year picker.
* @prop showFullMonthYearPicker - Flag to show full month year picker.
* @prop showTwoColumnMonthYearPicker - Flag to show two column month year picker.
* @prop showFourColumnMonthYearPicker - Flag to show four column month year picker.
* @prop showQuarterYearPicker - Flag to show quarter year picker.
* @prop showWeekPicker - Flag to show week picker.
* @prop isInputFocused - Flag to set input focus.
* @prop weekAriaLabelPrefix - Aria label prefix for week.
* @prop containerRef - The container reference.
* @prop monthShowsDuplicateDaysEnd - Flag to show duplicate days at the end of the month.
* @prop monthShowsDuplicateDaysStart - Flag to show duplicate days at the start of the month.
*
* @example
* ```tsx
* function App() {
* const handleDayClick = (date) => {
* console.log('Day clicked: ', date);
* };
*
* const handleDayMouseEnter = (date) => {
* console.log('Mouse entered on day: ', date);
* };
*
* return (
* <div>
* <Month
* day={new Date()}
* endDate={new Date()}
* onDayClick={handleDayClick}
* onDayMouseEnter={handleDayMouseEnter}
* disabledKeyboardNavigation={false}
* showWeekNumbers={true}
* showMonthYearPicker={false}
* />
* </div>
* );
* }
* ```
*/
export default class Month extends Component<MonthProps> {
MONTH_REFS: React.RefObject<HTMLDivElement | null>[];
QUARTER_REFS: React.RefObject<HTMLDivElement | null>[];
isDisabled: (day: Date) => boolean;
isExcluded: (day: Date) => boolean;
handleDayClick: (day: Date, event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
handleDayMouseEnter: (day: Date) => void;
handleMouseLeave: () => void;
isRangeStartMonth: (m: number) => boolean;
isRangeStartQuarter: (q: number) => boolean;
isRangeEndMonth: (m: number) => boolean;
isRangeEndQuarter: (q: number) => boolean;
isInSelectingRangeMonth: (m: number) => boolean;
isSelectingMonthRangeStart: (m: number) => boolean;
isSelectingMonthRangeEnd: (m: number) => boolean;
isInSelectingRangeQuarter: (q: number) => boolean;
isWeekInMonth: (startOfWeek: Date) => boolean;
isCurrentMonth: (day: Date, m: number) => boolean;
isCurrentQuarter: (day: Date, q: number) => boolean;
isSelectedMonth: (day: Date, m: number, selected: Date) => boolean;
isSelectMonthInList: (day: Date, m: number, selectedDates: Date[]) => boolean;
isSelectedQuarter: (day: Date, q: number, selected: Date) => boolean;
isSelectQuarterInList: (day: Date, q: number, selectedDates: Date[]) => boolean;
isMonthSelected: () => boolean | undefined;
isQuarterSelected: () => boolean | undefined;
renderWeeks: () => React.JSX.Element[];
onMonthClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.KeyboardEvent<HTMLDivElement>, m: number) => void;
onMonthMouseEnter: (m: number) => void;
handleMonthNavigation: (newMonth: number, newDate: Date) => void;
handleKeyboardNavigation: (event: React.KeyboardEvent<HTMLDivElement>, eventKey: KeyType, month: number) => void;
getVerticalOffset: (monthColumnsLayout: string) => number;
onMonthKeyDown: (event: React.KeyboardEvent<HTMLDivElement>, month: number) => void;
onQuarterClick: (event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>, q: number) => void;
onQuarterMouseEnter: (q: number) => void;
handleQuarterNavigation: (newQuarter: number, newDate: Date) => void;
onQuarterKeyDown: (event: React.KeyboardEvent<HTMLDivElement>, quarter: number) => void;
isMonthDisabledForLabelDate: (month: number) => {
isDisabled: boolean;
labelDate: Date;
};
isMonthDisabled: (month: number) => boolean;
getSelection(): Date[] | undefined;
getMonthClassNames: (m: number) => string;
getTabIndex: (m: number) => "-1" | "0";
getQuarterTabIndex: (q: number) => "-1" | "0";
getAriaLabel: (month: number) => string;
getQuarterClassNames: (q: number) => string;
getMonthContent: (m: number) => React.ReactNode;
getQuarterContent: (q: number) => string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined>;
renderMonths: () => React.JSX.Element[] | undefined;
renderQuarters: () => React.JSX.Element;
getClassNames: () => string;
render(): React.JSX.Element;
}
export {};
+26
View File
@@ -0,0 +1,26 @@
import React, { Component } from "react";
import { type Locale } from "./date_utils";
import MonthDropdownOptions from "./month_dropdown_options";
interface MonthDropdownOptionsProps extends React.ComponentPropsWithoutRef<typeof MonthDropdownOptions> {
}
interface MonthDropdownProps extends Omit<MonthDropdownOptionsProps, "monthNames" | "onChange" | "onCancel"> {
dropdownMode: "scroll" | "select";
locale?: Locale;
onChange: (month: number) => void;
useShortMonthInDropdown?: boolean;
}
interface MonthDropdownState {
dropdownVisible: boolean;
}
export default class MonthDropdown extends Component<MonthDropdownProps, MonthDropdownState> {
state: MonthDropdownState;
renderSelectOptions: (monthNames: string[]) => React.ReactElement[];
renderSelectMode: (monthNames: string[]) => React.ReactElement;
renderReadView: (visible: boolean, monthNames: string[]) => React.ReactElement;
renderDropdown: (monthNames: string[]) => React.ReactElement;
renderScrollMode: (monthNames: string[]) => React.ReactElement[];
onChange: (month: number) => void;
toggleDropdown: () => void;
render(): React.ReactElement;
}
export {};
@@ -0,0 +1,17 @@
import React, { Component } from "react";
interface MonthDropdownOptionsProps {
onCancel: VoidFunction;
onChange: (month: number) => void;
month: number;
monthNames: string[];
}
export default class MonthDropdownOptions extends Component<MonthDropdownOptionsProps> {
monthOptionButtonsRef: Record<number, HTMLDivElement | null>;
isSelectedMonth: (i: number) => boolean;
handleOptionKeyDown: (i: number, e: React.KeyboardEvent) => void;
renderOptions: () => React.ReactElement[];
onChange: (month: number) => void;
handleClickOutside: () => void;
render(): React.ReactElement;
}
export {};
+26
View File
@@ -0,0 +1,26 @@
import React, { Component } from "react";
import { type Locale } from "./date_utils";
import MonthYearDropdownOptions from "./month_year_dropdown_options";
interface MonthYearDropdownOptionsProps extends React.ComponentPropsWithoutRef<typeof MonthYearDropdownOptions> {
}
interface MonthYearDropdownProps extends Omit<MonthYearDropdownOptionsProps, "onChange" | "onCancel"> {
dropdownMode: "scroll" | "select";
onChange: (monthYear: Date) => void;
locale?: Locale;
}
interface MonthYearDropdownState {
dropdownVisible: boolean;
}
export default class MonthYearDropdown extends Component<MonthYearDropdownProps, MonthYearDropdownState> {
state: MonthYearDropdownState;
renderSelectOptions: () => React.ReactElement[];
onSelectChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
renderSelectMode: () => React.ReactElement;
renderReadView: (visible: boolean) => React.ReactElement;
renderDropdown: () => React.ReactElement;
renderScrollMode: () => React.ReactElement[];
onChange: (monthYearPoint: number) => void;
toggleDropdown: () => void;
render(): React.ReactElement;
}
export {};
@@ -0,0 +1,23 @@
import React, { Component } from "react";
import { type Locale } from "./date_utils";
interface MonthYearDropdownOptionsProps {
minDate?: Date;
maxDate?: Date;
onCancel: VoidFunction;
onChange: (monthYear: number) => void;
scrollableMonthYearDropdown?: boolean;
date: Date;
dateFormat: string;
locale?: Locale;
}
interface MonthYearDropdownOptionsState {
monthYearsList: Date[];
}
export default class MonthYearDropdownOptions extends Component<MonthYearDropdownOptionsProps, MonthYearDropdownOptionsState> {
constructor(props: MonthYearDropdownOptionsProps);
renderOptions: () => React.ReactElement[];
onChange: (monthYear: number) => void;
handleClickOutside: () => void;
render(): React.ReactElement;
}
export {};
+29
View File
@@ -0,0 +1,29 @@
import React from "react";
import Portal from "./portal";
import TabLoop from "./tab_loop";
import type { FloatingProps } from "./with_floating";
import type { ReactNode } from "react";
interface PortalProps extends Omit<React.ComponentPropsWithoutRef<typeof Portal>, "children"> {
}
interface TabLoopProps extends Omit<React.ComponentPropsWithoutRef<typeof TabLoop>, "children"> {
}
interface PopperComponentProps extends Omit<PortalProps, "portalId">, TabLoopProps, FloatingProps {
className?: string;
wrapperClassName?: string;
popperComponent: React.ReactNode;
popperContainer?: React.FC<{
children?: ReactNode | undefined;
}>;
targetComponent: React.ReactNode;
popperOnKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
showArrow?: boolean;
portalId?: PortalProps["portalId"];
popperTargetRef?: React.RefObject<HTMLElement | null>;
monthHeaderPosition?: "top" | "middle" | "bottom";
}
export declare const PopperComponent: React.FC<PopperComponentProps>;
declare const _default: {
(props: Omit<PopperComponentProps, "popperProps"> & import("./with_floating").WithFloatingProps): React.ReactElement;
displayName: string;
};
export default _default;
+26
View File
@@ -0,0 +1,26 @@
import { Component } from "react";
import type React from "react";
interface PortalProps {
children: React.ReactNode;
portalId: string;
portalHost?: ShadowRoot;
}
/**
* `Portal` is a React component that allows you to render children into a DOM node
* that exists outside the DOM hierarchy of the parent component.
*
* @class
* @param {PortalProps} props - The properties that define the `Portal` component.
* @property {React.ReactNode} props.children - The children to be rendered into the `Portal`.
* @property {string} props.portalId - The id of the DOM node into which the `Portal` will render.
* @property {ShadowRoot} [props.portalHost] - The DOM node to host the `Portal`.
*/
declare class Portal extends Component<PortalProps> {
constructor(props: PortalProps);
componentDidMount(): void;
componentWillUnmount(): void;
private el;
private portalRoot;
render(): React.ReactPortal;
}
export default Portal;
@@ -0,0 +1,771 @@
@charset "UTF-8";
:global .react-datepicker__navigation-icon::before, :global .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-year-read-view--down-arrow {
border-color: #ccc;
border-style: solid;
border-width: 3px 3px 0 0;
content: "";
display: block;
height: 9px;
position: absolute;
top: 6px;
width: 9px;
}
:global .react-datepicker__sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
:global .react-datepicker-wrapper {
display: inline-block;
padding: 0;
border: 0;
}
:global .react-datepicker {
font-family: "Helvetica Neue", helvetica, arial, sans-serif;
font-size: 0.8rem;
background-color: #fff;
color: #000;
border: 1px solid #aeaeae;
border-radius: 0.3rem;
display: inline-block;
position: relative;
line-height: initial;
}
:global .react-datepicker--time-only .react-datepicker__time-container {
border-left: 0;
}
:global .react-datepicker--time-only .react-datepicker__time,
:global .react-datepicker--time-only .react-datepicker__time-box {
border-bottom-left-radius: 0.375em;
border-bottom-right-radius: 0.375em;
}
:global .react-datepicker-popper {
z-index: 1;
line-height: 0;
}
:global .react-datepicker-popper .react-datepicker__triangle {
stroke: #aeaeae;
}
:global .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle {
fill: #f0f0f0;
color: #f0f0f0;
}
:global .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle {
fill: #fff;
color: #fff;
}
:global .react-datepicker-popper--header-middle[data-placement^=bottom] .react-datepicker__triangle, :global .react-datepicker-popper--header-bottom[data-placement^=bottom] .react-datepicker__triangle {
fill: #fff;
color: #fff;
}
:global .react-datepicker-popper--header-bottom[data-placement^=top] .react-datepicker__triangle {
fill: #f0f0f0;
color: #f0f0f0;
}
:global .react-datepicker__header {
text-align: center;
background-color: #f0f0f0;
border-bottom: 1px solid #aeaeae;
border-top-left-radius: 0.3rem;
padding: 8px 0;
position: relative;
}
:global .react-datepicker__header--time {
padding-bottom: 8px;
padding-left: 5px;
padding-right: 5px;
}
:global .react-datepicker__header--time:not(.react-datepicker__header--time--only) {
border-top-left-radius: 0;
}
:global .react-datepicker__header:not(.react-datepicker__header--has-time-select, .react-datepicker__header--middle, .react-datepicker__header--bottom) {
border-top-right-radius: 0.3rem;
}
:global .react-datepicker__header--middle {
border-top: 1px solid #aeaeae;
border-radius: 0;
margin-top: 4px;
}
:global .react-datepicker__header--bottom {
border-bottom: none;
border-top: 1px solid #aeaeae;
border-radius: 0 0 0.3rem 0.3rem;
}
:global .react-datepicker__header-wrapper {
position: relative;
}
:global .react-datepicker__header-wrapper .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
right: 2px;
}
:global .react-datepicker__year-dropdown-container--select,
:global .react-datepicker__month-dropdown-container--select,
:global .react-datepicker__month-year-dropdown-container--select,
:global .react-datepicker__year-dropdown-container--scroll,
:global .react-datepicker__month-dropdown-container--scroll,
:global .react-datepicker__month-year-dropdown-container--scroll {
display: inline-block;
margin: 0 15px;
}
:global .react-datepicker__month-select,
:global .react-datepicker__year-select,
:global .react-datepicker__month-year-select {
background-color: transparent;
border: 1px solid #aeaeae;
border-radius: 0.3rem;
color: inherit;
cursor: pointer;
font-family: inherit;
font-size: inherit;
margin-top: 5px;
padding: 2px 5px;
}
:global .react-datepicker__month-select:focus-visible,
:global .react-datepicker__year-select:focus-visible,
:global .react-datepicker__month-year-select:focus-visible {
outline: auto 1px;
}
:global .react-datepicker__current-month,
:global .react-datepicker-time__header,
:global .react-datepicker-year-header {
margin-top: 0;
color: #000;
font-weight: bold;
font-size: 0.944rem;
}
:global h2.react-datepicker__current-month {
padding: 0;
margin: 0;
}
:global .react-datepicker-time__header {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
:global .react-datepicker__navigation {
align-items: center;
background: none;
display: flex;
justify-content: center;
text-align: center;
cursor: pointer;
position: absolute;
top: 2px;
padding: 0;
border: none;
z-index: 1;
height: 32px;
width: 32px;
text-indent: -999em;
overflow: hidden;
}
:global .react-datepicker__navigation--previous {
left: 2px;
}
:global .react-datepicker__navigation--next {
right: 2px;
}
:global .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
right: 85px;
}
:global .react-datepicker__navigation--years {
position: relative;
top: 0;
display: block;
margin-left: auto;
margin-right: auto;
}
:global .react-datepicker__navigation--years-previous {
top: 4px;
}
:global .react-datepicker__navigation--years-upcoming {
top: -4px;
}
:global .react-datepicker__navigation:hover *::before {
border-color: rgb(165.75, 165.75, 165.75);
}
:global .react-datepicker__navigation-icon {
position: relative;
top: -1px;
font-size: 20px;
width: 0;
}
:global .react-datepicker__navigation-icon--next {
left: -2px;
}
:global .react-datepicker__navigation-icon--next::before {
transform: rotate(45deg);
left: -7px;
}
:global .react-datepicker__navigation-icon--previous {
right: -2px;
}
:global .react-datepicker__navigation-icon--previous::before {
transform: rotate(225deg);
right: -7px;
}
:global .react-datepicker__month-container {
float: left;
}
:global .react-datepicker__year {
margin: 0.5em;
text-align: center;
}
:global .react-datepicker__year-wrapper {
display: flex;
flex-wrap: wrap;
max-width: 180px;
}
:global .react-datepicker__year .react-datepicker__year-text {
display: inline-block;
width: 5em;
margin: 2px;
}
:global .react-datepicker__month {
margin: 0.5em;
text-align: center;
}
:global .react-datepicker__month .react-datepicker__month-text,
:global .react-datepicker__month .react-datepicker__quarter-text {
display: inline-block;
width: 5em;
margin: 2px;
}
:global .react-datepicker__input-time-container {
clear: both;
width: 100%;
float: left;
margin: 5px 0 10px 15px;
text-align: left;
}
:global .react-datepicker__input-time-container .react-datepicker-time__caption {
display: inline-block;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container {
display: inline-block;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input {
display: inline-block;
margin-left: 10px;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input {
width: auto;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-inner-spin-button,
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time] {
-moz-appearance: textfield;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__delimiter {
margin-left: 5px;
display: inline-block;
}
:global .react-datepicker__time-container {
float: right;
border-left: 1px solid #aeaeae;
width: 85px;
}
:global .react-datepicker__time-container--with-today-button {
display: inline;
border: 1px solid #aeaeae;
border-radius: 0.375em;
position: absolute;
right: -87px;
top: 0;
}
:global .react-datepicker__time-container .react-datepicker__time {
position: relative;
background: white;
border-bottom-right-radius: 0.375em;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box {
width: 85px;
overflow-x: hidden;
margin: 0 auto;
text-align: center;
border-bottom-right-radius: 0.375em;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list {
list-style: none;
margin: 0;
height: calc(195px + 2.125em / 2);
overflow-y: scroll;
padding-right: 0;
padding-left: 0;
width: 100%;
box-sizing: content-box;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item {
height: 30px;
padding: 5px 10px;
white-space: nowrap;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover {
cursor: pointer;
background-color: #f0f0f0;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected {
background-color: #216ba5;
color: white;
font-weight: bold;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected:hover {
background-color: #216ba5;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled {
color: #ccc;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled:hover {
cursor: default;
background-color: transparent;
}
:global .react-datepicker__week-number {
color: #ccc;
display: inline-block;
width: 2.125em;
line-height: 2.125em;
text-align: center;
margin: 0.208em;
}
:global .react-datepicker__week-number.react-datepicker__week-number--clickable {
cursor: pointer;
}
:global .react-datepicker__week-number.react-datepicker__week-number--clickable:not(.react-datepicker__week-number--selected):hover {
border-radius: 0.3rem;
background-color: #f0f0f0;
}
:global .react-datepicker__week-number--selected {
border-radius: 0.3rem;
background-color: #216ba5;
color: #fff;
}
:global .react-datepicker__week-number--selected:hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
}
:global .react-datepicker__day-names {
text-align: center;
white-space: nowrap;
margin-bottom: -8px;
}
:global .react-datepicker__week {
white-space: nowrap;
}
:global .react-datepicker__day-name,
:global .react-datepicker__day,
:global .react-datepicker__time-name {
color: #000;
display: inline-block;
width: 2.125em;
line-height: 2.125em;
text-align: center;
margin: 0.208em;
}
:global .react-datepicker__day-name--disabled,
:global .react-datepicker__day--disabled,
:global .react-datepicker__time-name--disabled {
cursor: default;
color: #ccc;
}
:global .react-datepicker__day,
:global .react-datepicker__month-text,
:global .react-datepicker__quarter-text,
:global .react-datepicker__year-text {
cursor: pointer;
}
:global .react-datepicker__day:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text:not([aria-disabled=true]):hover {
border-radius: 0.3rem;
background-color: #f0f0f0;
}
:global .react-datepicker__day--today,
:global .react-datepicker__month-text--today,
:global .react-datepicker__quarter-text--today,
:global .react-datepicker__year-text--today {
font-weight: bold;
}
:global .react-datepicker__day--highlighted,
:global .react-datepicker__month-text--highlighted,
:global .react-datepicker__quarter-text--highlighted,
:global .react-datepicker__year-text--highlighted {
border-radius: 0.3rem;
background-color: #3dcc4a;
color: #fff;
}
:global .react-datepicker__day--highlighted:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--highlighted:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--highlighted:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--highlighted:not([aria-disabled=true]):hover {
background-color: rgb(49.8551020408, 189.6448979592, 62.5632653061);
}
:global .react-datepicker__day--highlighted-custom-1,
:global .react-datepicker__month-text--highlighted-custom-1,
:global .react-datepicker__quarter-text--highlighted-custom-1,
:global .react-datepicker__year-text--highlighted-custom-1 {
color: magenta;
}
:global .react-datepicker__day--highlighted-custom-2,
:global .react-datepicker__month-text--highlighted-custom-2,
:global .react-datepicker__quarter-text--highlighted-custom-2,
:global .react-datepicker__year-text--highlighted-custom-2 {
color: green;
}
:global .react-datepicker__day--holidays,
:global .react-datepicker__month-text--holidays,
:global .react-datepicker__quarter-text--holidays,
:global .react-datepicker__year-text--holidays {
position: relative;
border-radius: 0.3rem;
background-color: #ff6803;
color: #fff;
}
:global .react-datepicker__day--holidays .overlay,
:global .react-datepicker__month-text--holidays .overlay,
:global .react-datepicker__quarter-text--holidays .overlay,
:global .react-datepicker__year-text--holidays .overlay {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;
}
:global .react-datepicker__day--holidays:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--holidays:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--holidays:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--holidays:not([aria-disabled=true]):hover {
background-color: rgb(207, 82.9642857143, 0);
}
:global .react-datepicker__day--holidays:hover .overlay,
:global .react-datepicker__month-text--holidays:hover .overlay,
:global .react-datepicker__quarter-text--holidays:hover .overlay,
:global .react-datepicker__year-text--holidays:hover .overlay {
visibility: visible;
opacity: 1;
}
:global .react-datepicker__day--selected, :global .react-datepicker__day--in-selecting-range, :global .react-datepicker__day--in-range,
:global .react-datepicker__month-text--selected,
:global .react-datepicker__month-text--in-selecting-range,
:global .react-datepicker__month-text--in-range,
:global .react-datepicker__quarter-text--selected,
:global .react-datepicker__quarter-text--in-selecting-range,
:global .react-datepicker__quarter-text--in-range,
:global .react-datepicker__year-text--selected,
:global .react-datepicker__year-text--in-selecting-range,
:global .react-datepicker__year-text--in-range {
border-radius: 0.3rem;
background-color: #216ba5;
color: #fff;
}
:global .react-datepicker__day--selected:not([aria-disabled=true]):hover, :global .react-datepicker__day--in-selecting-range:not([aria-disabled=true]):hover, :global .react-datepicker__day--in-range:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--selected:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--in-selecting-range:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--in-range:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--selected:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--in-selecting-range:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--in-range:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--selected:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--in-selecting-range:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--in-range:not([aria-disabled=true]):hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
}
:global .react-datepicker__day--keyboard-selected,
:global .react-datepicker__month-text--keyboard-selected,
:global .react-datepicker__quarter-text--keyboard-selected,
:global .react-datepicker__year-text--keyboard-selected {
border-radius: 0.3rem;
background-color: rgb(186.25, 217.0833333333, 241.25);
color: rgb(0, 0, 0);
}
:global .react-datepicker__day--keyboard-selected:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--keyboard-selected:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--keyboard-selected:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--keyboard-selected:not([aria-disabled=true]):hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
color: #fff;
}
:global .react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
:global .react-datepicker__month-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
:global .react-datepicker__quarter-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
:global .react-datepicker__year-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range) {
background-color: rgba(33, 107, 165, 0.5);
}
:global .react-datepicker__month--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range), :global .react-datepicker__year--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__month--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__year--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__month--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__year--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__month--selecting-range .react-datepicker__year-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__year--selecting-range .react-datepicker__year-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range) {
background-color: #f0f0f0;
color: #000;
}
:global .react-datepicker__day--disabled,
:global .react-datepicker__month-text--disabled,
:global .react-datepicker__quarter-text--disabled,
:global .react-datepicker__year-text--disabled {
cursor: default;
color: #ccc;
}
:global .react-datepicker__day--disabled .overlay,
:global .react-datepicker__month-text--disabled .overlay,
:global .react-datepicker__quarter-text--disabled .overlay,
:global .react-datepicker__year-text--disabled .overlay {
position: absolute;
bottom: 70%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;
}
:global .react-datepicker__input-container {
position: relative;
display: inline-block;
width: 100%;
}
:global .react-datepicker__input-container .react-datepicker__calendar-icon {
position: absolute;
padding: 0.625em;
box-sizing: content-box;
}
:global .react-datepicker__view-calendar-icon input {
padding: 6px 10px 5px 25px;
}
:global .react-datepicker__year-read-view,
:global .react-datepicker__month-read-view,
:global .react-datepicker__month-year-read-view {
border: 1px solid transparent;
border-radius: 0.3rem;
position: relative;
}
:global .react-datepicker__year-read-view:hover,
:global .react-datepicker__month-read-view:hover,
:global .react-datepicker__month-year-read-view:hover {
cursor: pointer;
}
:global .react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__year-read-view:hover .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-read-view:hover .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-year-read-view:hover .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-year-read-view:hover .react-datepicker__month-read-view--down-arrow {
border-top-color: rgb(178.5, 178.5, 178.5);
}
:global .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-year-read-view--down-arrow {
transform: rotate(135deg);
right: -16px;
top: 0;
}
:global .react-datepicker__year-dropdown,
:global .react-datepicker__month-dropdown,
:global .react-datepicker__month-year-dropdown {
background-color: #f0f0f0;
position: absolute;
width: 50%;
left: 25%;
top: 30px;
z-index: 1;
text-align: center;
border-radius: 0.3rem;
border: 1px solid #aeaeae;
}
:global .react-datepicker__year-dropdown:hover,
:global .react-datepicker__month-dropdown:hover,
:global .react-datepicker__month-year-dropdown:hover {
cursor: pointer;
}
:global .react-datepicker__year-dropdown--scrollable,
:global .react-datepicker__month-dropdown--scrollable,
:global .react-datepicker__month-year-dropdown--scrollable {
height: 150px;
overflow-y: scroll;
}
:global .react-datepicker__year-option,
:global .react-datepicker__month-option,
:global .react-datepicker__month-year-option {
line-height: 20px;
width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
}
:global .react-datepicker__year-option:first-of-type,
:global .react-datepicker__month-option:first-of-type,
:global .react-datepicker__month-year-option:first-of-type {
border-top-left-radius: 0.3rem;
border-top-right-radius: 0.3rem;
}
:global .react-datepicker__year-option:last-of-type,
:global .react-datepicker__month-option:last-of-type,
:global .react-datepicker__month-year-option:last-of-type {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-bottom-left-radius: 0.3rem;
border-bottom-right-radius: 0.3rem;
}
:global .react-datepicker__year-option:hover,
:global .react-datepicker__month-option:hover,
:global .react-datepicker__month-year-option:hover {
background-color: #ccc;
}
:global .react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming,
:global .react-datepicker__month-option:hover .react-datepicker__navigation--years-upcoming,
:global .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-upcoming {
border-bottom-color: rgb(178.5, 178.5, 178.5);
}
:global .react-datepicker__year-option:hover .react-datepicker__navigation--years-previous,
:global .react-datepicker__month-option:hover .react-datepicker__navigation--years-previous,
:global .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-previous {
border-top-color: rgb(178.5, 178.5, 178.5);
}
:global .react-datepicker__year-option--selected,
:global .react-datepicker__month-option--selected,
:global .react-datepicker__month-year-option--selected {
position: absolute;
left: 15px;
}
:global .react-datepicker__close-icon {
cursor: pointer;
background-color: transparent;
border: 0;
outline: 0;
padding: 0 6px 0 0;
position: absolute;
top: 0;
right: 0;
height: 100%;
display: table-cell;
vertical-align: middle;
}
:global .react-datepicker__close-icon::after {
cursor: pointer;
background-color: #216ba5;
color: #fff;
border-radius: 50%;
height: 16px;
width: 16px;
padding: 2px;
font-size: 12px;
line-height: 1;
text-align: center;
display: table-cell;
vertical-align: middle;
content: "×";
}
:global .react-datepicker__close-icon--disabled {
cursor: default;
}
:global .react-datepicker__close-icon--disabled::after {
cursor: default;
background-color: #ccc;
}
:global .react-datepicker__today-button {
background: #f0f0f0;
border-top: 1px solid #aeaeae;
cursor: pointer;
text-align: center;
font-weight: bold;
padding: 5px 0;
clear: left;
}
:global .react-datepicker__portal {
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
left: 0;
top: 0;
justify-content: center;
align-items: center;
display: flex;
z-index: 2147483647;
}
:global .react-datepicker__children-container {
width: 17.25em;
margin: 0.5em;
padding-right: 0.25em;
padding-left: 0.25em;
height: auto;
}
:global .react-datepicker__aria-live {
position: absolute;
clip-path: circle(0);
border: 0;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
width: 1px;
white-space: nowrap;
}
:global .react-datepicker__calendar-icon {
width: 1em;
height: 1em;
vertical-align: -0.125em;
}
:global .react-datepicker-popper-offset {
margin-top: -0.7em;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+806
View File
@@ -0,0 +1,806 @@
@charset "UTF-8";
.react-datepicker__navigation-icon::before, .react-datepicker__year-read-view--down-arrow,
.react-datepicker__month-read-view--down-arrow,
.react-datepicker__month-year-read-view--down-arrow {
border-color: #ccc;
border-style: solid;
border-width: 3px 3px 0 0;
content: "";
display: block;
height: 9px;
position: absolute;
top: 6px;
width: 9px;
}
.react-datepicker__sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.react-datepicker-wrapper {
display: inline-block;
padding: 0;
border: 0;
}
.react-datepicker {
font-family: "Helvetica Neue", helvetica, arial, sans-serif;
font-size: 0.8rem;
background-color: #fff;
color: #000;
border: 1px solid #aeaeae;
border-radius: 0.3rem;
display: inline-block;
position: relative;
line-height: initial;
}
.react-datepicker--time-only .react-datepicker__time-container {
border-left: 0;
}
.react-datepicker--time-only .react-datepicker__time,
.react-datepicker--time-only .react-datepicker__time-box {
border-bottom-left-radius: 0.375em;
border-bottom-right-radius: 0.375em;
}
.react-datepicker-popper {
z-index: 1;
line-height: 0;
}
.react-datepicker-popper .react-datepicker__triangle {
stroke: #aeaeae;
}
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle {
fill: #f0f0f0;
color: #f0f0f0;
}
.react-datepicker-popper[data-placement^=top] .react-datepicker__triangle {
fill: #fff;
color: #fff;
}
.react-datepicker-popper--header-middle[data-placement^=bottom] .react-datepicker__triangle, .react-datepicker-popper--header-bottom[data-placement^=bottom] .react-datepicker__triangle {
fill: #fff;
color: #fff;
}
.react-datepicker-popper--header-bottom[data-placement^=top] .react-datepicker__triangle {
fill: #f0f0f0;
color: #f0f0f0;
}
.react-datepicker__header {
text-align: center;
background-color: #f0f0f0;
border-bottom: 1px solid #aeaeae;
border-top-left-radius: 0.3rem;
padding: 8px 0;
position: relative;
}
.react-datepicker__header--time {
padding-bottom: 8px;
padding-left: 5px;
padding-right: 5px;
}
.react-datepicker__header--time:not(.react-datepicker__header--time--only) {
border-top-left-radius: 0;
}
.react-datepicker__header:not(.react-datepicker__header--has-time-select, .react-datepicker__header--middle, .react-datepicker__header--bottom) {
border-top-right-radius: 0.3rem;
}
.react-datepicker__header--middle {
border-top: 1px solid #aeaeae;
border-radius: 0;
margin-top: 4px;
}
.react-datepicker__header--bottom {
border-bottom: none;
border-top: 1px solid #aeaeae;
border-radius: 0 0 0.3rem 0.3rem;
}
.react-datepicker__header-wrapper {
position: relative;
}
.react-datepicker__header-wrapper .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
right: 2px;
}
.react-datepicker__year-dropdown-container--select,
.react-datepicker__month-dropdown-container--select,
.react-datepicker__month-year-dropdown-container--select,
.react-datepicker__year-dropdown-container--scroll,
.react-datepicker__month-dropdown-container--scroll,
.react-datepicker__month-year-dropdown-container--scroll {
display: inline-block;
margin: 0 15px;
}
.react-datepicker__month-select,
.react-datepicker__year-select,
.react-datepicker__month-year-select {
background-color: transparent;
border: 1px solid #aeaeae;
border-radius: 0.3rem;
color: inherit;
cursor: pointer;
font-family: inherit;
font-size: inherit;
margin-top: 5px;
padding: 2px 5px;
}
.react-datepicker__month-select:focus-visible,
.react-datepicker__year-select:focus-visible,
.react-datepicker__month-year-select:focus-visible {
outline: auto 1px;
}
.react-datepicker__current-month,
.react-datepicker-time__header,
.react-datepicker-year-header {
margin-top: 0;
color: #000;
font-weight: bold;
font-size: 0.944rem;
}
h2.react-datepicker__current-month {
padding: 0;
margin: 0;
}
.react-datepicker-time__header {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.react-datepicker__navigation {
align-items: center;
background: none;
display: flex;
justify-content: center;
text-align: center;
cursor: pointer;
position: absolute;
top: 2px;
padding: 0;
border: none;
z-index: 1;
height: 32px;
width: 32px;
text-indent: -999em;
overflow: hidden;
}
.react-datepicker__navigation--previous {
left: 2px;
}
.react-datepicker__navigation--next {
right: 2px;
}
.react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
right: 85px;
}
.react-datepicker__navigation--years {
position: relative;
top: 0;
display: block;
margin-left: auto;
margin-right: auto;
}
.react-datepicker__navigation--years-previous {
top: 4px;
}
.react-datepicker__navigation--years-upcoming {
top: -4px;
}
.react-datepicker__navigation:hover *::before {
border-color: rgb(165.75, 165.75, 165.75);
}
.react-datepicker__navigation-icon {
position: relative;
top: -1px;
font-size: 20px;
width: 0;
}
.react-datepicker__navigation-icon--next {
left: -2px;
}
.react-datepicker__navigation-icon--next::before {
transform: rotate(45deg);
left: -7px;
}
.react-datepicker__navigation-icon--previous {
right: -2px;
}
.react-datepicker__navigation-icon--previous::before {
transform: rotate(225deg);
right: -7px;
}
.react-datepicker__month-container {
float: left;
}
.react-datepicker__year {
margin: 0.5em;
text-align: center;
}
.react-datepicker__year-wrapper {
display: flex;
flex-wrap: wrap;
max-width: 180px;
}
.react-datepicker__year .react-datepicker__year-text {
display: inline-block;
width: 5em;
margin: 2px;
}
.react-datepicker__month {
margin: 0.5em;
text-align: center;
}
.react-datepicker__month .react-datepicker__month-text,
.react-datepicker__month .react-datepicker__quarter-text {
display: inline-block;
width: 5em;
margin: 2px;
}
.react-datepicker__input-time-container {
clear: both;
width: 100%;
float: left;
margin: 5px 0 10px 15px;
text-align: left;
}
.react-datepicker__input-time-container .react-datepicker-time__caption {
display: inline-block;
}
.react-datepicker__input-time-container .react-datepicker-time__input-container {
display: inline-block;
}
.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input {
display: inline-block;
margin-left: 10px;
}
.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input {
width: auto;
}
.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-inner-spin-button,
.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time] {
-moz-appearance: textfield;
}
.react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__delimiter {
margin-left: 5px;
display: inline-block;
}
.react-datepicker__time-container {
float: right;
border-left: 1px solid #aeaeae;
width: 85px;
}
.react-datepicker__time-container--with-today-button {
display: inline;
border: 1px solid #aeaeae;
border-radius: 0.375em;
position: absolute;
right: -87px;
top: 0;
}
.react-datepicker__time-container .react-datepicker__time {
position: relative;
background: white;
border-bottom-right-radius: 0.375em;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box {
width: 85px;
overflow-x: hidden;
margin: 0 auto;
text-align: center;
border-bottom-right-radius: 0.375em;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list {
list-style: none;
margin: 0;
height: calc(195px + 2.125em / 2);
overflow-y: scroll;
padding-right: 0;
padding-left: 0;
width: 100%;
box-sizing: content-box;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item {
height: 30px;
padding: 5px 10px;
white-space: nowrap;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover {
cursor: pointer;
background-color: #f0f0f0;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected {
background-color: #216ba5;
color: white;
font-weight: bold;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected:hover {
background-color: #216ba5;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled {
color: #ccc;
}
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled:hover {
cursor: default;
background-color: transparent;
}
.react-datepicker__week-number {
color: #ccc;
display: inline-block;
width: 2.125em;
line-height: 2.125em;
text-align: center;
margin: 0.208em;
}
.react-datepicker__week-number.react-datepicker__week-number--clickable {
cursor: pointer;
}
.react-datepicker__week-number.react-datepicker__week-number--clickable:not(.react-datepicker__week-number--selected):hover {
border-radius: 0.3rem;
background-color: #f0f0f0;
}
.react-datepicker__week-number--selected {
border-radius: 0.3rem;
background-color: #216ba5;
color: #fff;
}
.react-datepicker__week-number--selected:hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
}
.react-datepicker__day-names {
text-align: center;
white-space: nowrap;
margin-bottom: -8px;
}
.react-datepicker__week {
white-space: nowrap;
}
.react-datepicker__day-name,
.react-datepicker__day,
.react-datepicker__time-name {
color: #000;
display: inline-block;
width: 2.125em;
line-height: 2.125em;
text-align: center;
margin: 0.208em;
}
.react-datepicker__day-name--disabled,
.react-datepicker__day--disabled,
.react-datepicker__time-name--disabled {
cursor: default;
color: #ccc;
}
.react-datepicker__day,
.react-datepicker__month-text,
.react-datepicker__quarter-text,
.react-datepicker__year-text {
cursor: pointer;
}
.react-datepicker__day:not([aria-disabled=true]):hover,
.react-datepicker__month-text:not([aria-disabled=true]):hover,
.react-datepicker__quarter-text:not([aria-disabled=true]):hover,
.react-datepicker__year-text:not([aria-disabled=true]):hover {
border-radius: 0.3rem;
background-color: #f0f0f0;
}
.react-datepicker__day--today,
.react-datepicker__month-text--today,
.react-datepicker__quarter-text--today,
.react-datepicker__year-text--today {
font-weight: bold;
}
.react-datepicker__day--highlighted,
.react-datepicker__month-text--highlighted,
.react-datepicker__quarter-text--highlighted,
.react-datepicker__year-text--highlighted {
border-radius: 0.3rem;
background-color: #3dcc4a;
color: #fff;
}
.react-datepicker__day--highlighted:not([aria-disabled=true]):hover,
.react-datepicker__month-text--highlighted:not([aria-disabled=true]):hover,
.react-datepicker__quarter-text--highlighted:not([aria-disabled=true]):hover,
.react-datepicker__year-text--highlighted:not([aria-disabled=true]):hover {
background-color: rgb(49.8551020408, 189.6448979592, 62.5632653061);
}
.react-datepicker__day--highlighted-custom-1,
.react-datepicker__month-text--highlighted-custom-1,
.react-datepicker__quarter-text--highlighted-custom-1,
.react-datepicker__year-text--highlighted-custom-1 {
color: magenta;
}
.react-datepicker__day--highlighted-custom-2,
.react-datepicker__month-text--highlighted-custom-2,
.react-datepicker__quarter-text--highlighted-custom-2,
.react-datepicker__year-text--highlighted-custom-2 {
color: green;
}
.react-datepicker__day--holidays,
.react-datepicker__month-text--holidays,
.react-datepicker__quarter-text--holidays,
.react-datepicker__year-text--holidays {
position: relative;
border-radius: 0.3rem;
background-color: #ff6803;
color: #fff;
}
.react-datepicker__day--holidays .overlay,
.react-datepicker__month-text--holidays .overlay,
.react-datepicker__quarter-text--holidays .overlay,
.react-datepicker__year-text--holidays .overlay {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;
}
.react-datepicker__day--holidays:not([aria-disabled=true]):hover,
.react-datepicker__month-text--holidays:not([aria-disabled=true]):hover,
.react-datepicker__quarter-text--holidays:not([aria-disabled=true]):hover,
.react-datepicker__year-text--holidays:not([aria-disabled=true]):hover {
background-color: rgb(207, 82.9642857143, 0);
}
.react-datepicker__day--holidays:hover .overlay,
.react-datepicker__month-text--holidays:hover .overlay,
.react-datepicker__quarter-text--holidays:hover .overlay,
.react-datepicker__year-text--holidays:hover .overlay {
visibility: visible;
opacity: 1;
}
.react-datepicker__day--selected, .react-datepicker__day--in-selecting-range, .react-datepicker__day--in-range,
.react-datepicker__month-text--selected,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--selected,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--selected,
.react-datepicker__year-text--in-selecting-range,
.react-datepicker__year-text--in-range {
border-radius: 0.3rem;
background-color: #216ba5;
color: #fff;
}
.react-datepicker__day--selected:not([aria-disabled=true]):hover, .react-datepicker__day--in-selecting-range:not([aria-disabled=true]):hover, .react-datepicker__day--in-range:not([aria-disabled=true]):hover,
.react-datepicker__month-text--selected:not([aria-disabled=true]):hover,
.react-datepicker__month-text--in-selecting-range:not([aria-disabled=true]):hover,
.react-datepicker__month-text--in-range:not([aria-disabled=true]):hover,
.react-datepicker__quarter-text--selected:not([aria-disabled=true]):hover,
.react-datepicker__quarter-text--in-selecting-range:not([aria-disabled=true]):hover,
.react-datepicker__quarter-text--in-range:not([aria-disabled=true]):hover,
.react-datepicker__year-text--selected:not([aria-disabled=true]):hover,
.react-datepicker__year-text--in-selecting-range:not([aria-disabled=true]):hover,
.react-datepicker__year-text--in-range:not([aria-disabled=true]):hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
}
.react-datepicker__day--keyboard-selected,
.react-datepicker__month-text--keyboard-selected,
.react-datepicker__quarter-text--keyboard-selected,
.react-datepicker__year-text--keyboard-selected {
border-radius: 0.3rem;
background-color: rgb(186.25, 217.0833333333, 241.25);
color: rgb(0, 0, 0);
}
.react-datepicker__day--keyboard-selected:not([aria-disabled=true]):hover,
.react-datepicker__month-text--keyboard-selected:not([aria-disabled=true]):hover,
.react-datepicker__quarter-text--keyboard-selected:not([aria-disabled=true]):hover,
.react-datepicker__year-text--keyboard-selected:not([aria-disabled=true]):hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
color: #fff;
}
.react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
.react-datepicker__month-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
.react-datepicker__quarter-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
.react-datepicker__year-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range) {
background-color: rgba(33, 107, 165, 0.5);
}
.react-datepicker__month--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range), .react-datepicker__year--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
.react-datepicker__month--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
.react-datepicker__year--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
.react-datepicker__month--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
.react-datepicker__year--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
.react-datepicker__month--selecting-range .react-datepicker__year-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
.react-datepicker__year--selecting-range .react-datepicker__year-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range) {
background-color: #f0f0f0;
color: #000;
}
.react-datepicker__day--disabled,
.react-datepicker__month-text--disabled,
.react-datepicker__quarter-text--disabled,
.react-datepicker__year-text--disabled {
cursor: default;
color: #ccc;
}
.react-datepicker__day--disabled .overlay,
.react-datepicker__month-text--disabled .overlay,
.react-datepicker__quarter-text--disabled .overlay,
.react-datepicker__year-text--disabled .overlay {
position: absolute;
bottom: 70%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;
}
.react-datepicker__input-container {
position: relative;
display: inline-block;
width: 100%;
}
.react-datepicker__input-container .react-datepicker__calendar-icon {
position: absolute;
padding: 0.625em;
box-sizing: content-box;
}
.react-datepicker__view-calendar-icon input {
padding: 6px 10px 5px 25px;
}
.react-datepicker__year-read-view,
.react-datepicker__month-read-view,
.react-datepicker__month-year-read-view {
border: 1px solid transparent;
border-radius: 0.3rem;
position: relative;
}
.react-datepicker__year-read-view:hover,
.react-datepicker__month-read-view:hover,
.react-datepicker__month-year-read-view:hover {
cursor: pointer;
}
.react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow,
.react-datepicker__year-read-view:hover .react-datepicker__month-read-view--down-arrow,
.react-datepicker__month-read-view:hover .react-datepicker__year-read-view--down-arrow,
.react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow,
.react-datepicker__month-year-read-view:hover .react-datepicker__year-read-view--down-arrow,
.react-datepicker__month-year-read-view:hover .react-datepicker__month-read-view--down-arrow {
border-top-color: rgb(178.5, 178.5, 178.5);
}
.react-datepicker__year-read-view--down-arrow,
.react-datepicker__month-read-view--down-arrow,
.react-datepicker__month-year-read-view--down-arrow {
transform: rotate(135deg);
right: -16px;
top: 0;
}
.react-datepicker__year-dropdown,
.react-datepicker__month-dropdown,
.react-datepicker__month-year-dropdown {
background-color: #f0f0f0;
position: absolute;
width: 50%;
left: 25%;
top: 30px;
z-index: 1;
text-align: center;
border-radius: 0.3rem;
border: 1px solid #aeaeae;
}
.react-datepicker__year-dropdown:hover,
.react-datepicker__month-dropdown:hover,
.react-datepicker__month-year-dropdown:hover {
cursor: pointer;
}
.react-datepicker__year-dropdown--scrollable,
.react-datepicker__month-dropdown--scrollable,
.react-datepicker__month-year-dropdown--scrollable {
height: 150px;
overflow-y: scroll;
}
.react-datepicker__year-option,
.react-datepicker__month-option,
.react-datepicker__month-year-option {
line-height: 20px;
width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
}
.react-datepicker__year-option:first-of-type,
.react-datepicker__month-option:first-of-type,
.react-datepicker__month-year-option:first-of-type {
border-top-left-radius: 0.3rem;
border-top-right-radius: 0.3rem;
}
.react-datepicker__year-option:last-of-type,
.react-datepicker__month-option:last-of-type,
.react-datepicker__month-year-option:last-of-type {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-bottom-left-radius: 0.3rem;
border-bottom-right-radius: 0.3rem;
}
.react-datepicker__year-option:hover,
.react-datepicker__month-option:hover,
.react-datepicker__month-year-option:hover {
background-color: #ccc;
}
.react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming,
.react-datepicker__month-option:hover .react-datepicker__navigation--years-upcoming,
.react-datepicker__month-year-option:hover .react-datepicker__navigation--years-upcoming {
border-bottom-color: rgb(178.5, 178.5, 178.5);
}
.react-datepicker__year-option:hover .react-datepicker__navigation--years-previous,
.react-datepicker__month-option:hover .react-datepicker__navigation--years-previous,
.react-datepicker__month-year-option:hover .react-datepicker__navigation--years-previous {
border-top-color: rgb(178.5, 178.5, 178.5);
}
.react-datepicker__year-option--selected,
.react-datepicker__month-option--selected,
.react-datepicker__month-year-option--selected {
position: absolute;
left: 15px;
}
.react-datepicker__close-icon {
cursor: pointer;
background-color: transparent;
border: 0;
outline: 0;
padding: 0 6px 0 0;
position: absolute;
top: 0;
right: 0;
height: 100%;
display: table-cell;
vertical-align: middle;
}
.react-datepicker__close-icon::after {
cursor: pointer;
background-color: #216ba5;
color: #fff;
border-radius: 50%;
height: 16px;
width: 16px;
padding: 2px;
font-size: 12px;
line-height: 1;
text-align: center;
display: table-cell;
vertical-align: middle;
content: "×";
}
.react-datepicker__close-icon--disabled {
cursor: default;
}
.react-datepicker__close-icon--disabled::after {
cursor: default;
background-color: #ccc;
}
.react-datepicker__today-button {
background: #f0f0f0;
border-top: 1px solid #aeaeae;
cursor: pointer;
text-align: center;
font-weight: bold;
padding: 5px 0;
clear: left;
}
.react-datepicker__portal {
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
left: 0;
top: 0;
justify-content: center;
align-items: center;
display: flex;
z-index: 2147483647;
}
.react-datepicker__children-container {
width: 17.25em;
margin: 0.5em;
padding-right: 0.25em;
padding-left: 0.25em;
height: auto;
}
.react-datepicker__aria-live {
position: absolute;
clip-path: circle(0);
border: 0;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
width: 1px;
white-space: nowrap;
}
.react-datepicker__calendar-icon {
width: 1em;
height: 1em;
vertical-align: -0.125em;
}
.react-datepicker-popper-offset {
margin-top: -0.7em;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+771
View File
@@ -0,0 +1,771 @@
@charset "UTF-8";
:global .react-datepicker__navigation-icon::before, :global .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-year-read-view--down-arrow {
border-color: #ccc;
border-style: solid;
border-width: 3px 3px 0 0;
content: "";
display: block;
height: 9px;
position: absolute;
top: 6px;
width: 9px;
}
:global .react-datepicker__sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
:global .react-datepicker-wrapper {
display: inline-block;
padding: 0;
border: 0;
}
:global .react-datepicker {
font-family: "Helvetica Neue", helvetica, arial, sans-serif;
font-size: 0.8rem;
background-color: #fff;
color: #000;
border: 1px solid #aeaeae;
border-radius: 0.3rem;
display: inline-block;
position: relative;
line-height: initial;
}
:global .react-datepicker--time-only .react-datepicker__time-container {
border-left: 0;
}
:global .react-datepicker--time-only .react-datepicker__time,
:global .react-datepicker--time-only .react-datepicker__time-box {
border-bottom-left-radius: 0.375em;
border-bottom-right-radius: 0.375em;
}
:global .react-datepicker-popper {
z-index: 1;
line-height: 0;
}
:global .react-datepicker-popper .react-datepicker__triangle {
stroke: #aeaeae;
}
:global .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle {
fill: #f0f0f0;
color: #f0f0f0;
}
:global .react-datepicker-popper[data-placement^=top] .react-datepicker__triangle {
fill: #fff;
color: #fff;
}
:global .react-datepicker-popper--header-middle[data-placement^=bottom] .react-datepicker__triangle, :global .react-datepicker-popper--header-bottom[data-placement^=bottom] .react-datepicker__triangle {
fill: #fff;
color: #fff;
}
:global .react-datepicker-popper--header-bottom[data-placement^=top] .react-datepicker__triangle {
fill: #f0f0f0;
color: #f0f0f0;
}
:global .react-datepicker__header {
text-align: center;
background-color: #f0f0f0;
border-bottom: 1px solid #aeaeae;
border-top-left-radius: 0.3rem;
padding: 8px 0;
position: relative;
}
:global .react-datepicker__header--time {
padding-bottom: 8px;
padding-left: 5px;
padding-right: 5px;
}
:global .react-datepicker__header--time:not(.react-datepicker__header--time--only) {
border-top-left-radius: 0;
}
:global .react-datepicker__header:not(.react-datepicker__header--has-time-select, .react-datepicker__header--middle, .react-datepicker__header--bottom) {
border-top-right-radius: 0.3rem;
}
:global .react-datepicker__header--middle {
border-top: 1px solid #aeaeae;
border-radius: 0;
margin-top: 4px;
}
:global .react-datepicker__header--bottom {
border-bottom: none;
border-top: 1px solid #aeaeae;
border-radius: 0 0 0.3rem 0.3rem;
}
:global .react-datepicker__header-wrapper {
position: relative;
}
:global .react-datepicker__header-wrapper .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
right: 2px;
}
:global .react-datepicker__year-dropdown-container--select,
:global .react-datepicker__month-dropdown-container--select,
:global .react-datepicker__month-year-dropdown-container--select,
:global .react-datepicker__year-dropdown-container--scroll,
:global .react-datepicker__month-dropdown-container--scroll,
:global .react-datepicker__month-year-dropdown-container--scroll {
display: inline-block;
margin: 0 15px;
}
:global .react-datepicker__month-select,
:global .react-datepicker__year-select,
:global .react-datepicker__month-year-select {
background-color: transparent;
border: 1px solid #aeaeae;
border-radius: 0.3rem;
color: inherit;
cursor: pointer;
font-family: inherit;
font-size: inherit;
margin-top: 5px;
padding: 2px 5px;
}
:global .react-datepicker__month-select:focus-visible,
:global .react-datepicker__year-select:focus-visible,
:global .react-datepicker__month-year-select:focus-visible {
outline: auto 1px;
}
:global .react-datepicker__current-month,
:global .react-datepicker-time__header,
:global .react-datepicker-year-header {
margin-top: 0;
color: #000;
font-weight: bold;
font-size: 0.944rem;
}
:global h2.react-datepicker__current-month {
padding: 0;
margin: 0;
}
:global .react-datepicker-time__header {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
:global .react-datepicker__navigation {
align-items: center;
background: none;
display: flex;
justify-content: center;
text-align: center;
cursor: pointer;
position: absolute;
top: 2px;
padding: 0;
border: none;
z-index: 1;
height: 32px;
width: 32px;
text-indent: -999em;
overflow: hidden;
}
:global .react-datepicker__navigation--previous {
left: 2px;
}
:global .react-datepicker__navigation--next {
right: 2px;
}
:global .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
right: 85px;
}
:global .react-datepicker__navigation--years {
position: relative;
top: 0;
display: block;
margin-left: auto;
margin-right: auto;
}
:global .react-datepicker__navigation--years-previous {
top: 4px;
}
:global .react-datepicker__navigation--years-upcoming {
top: -4px;
}
:global .react-datepicker__navigation:hover *::before {
border-color: rgb(165.75, 165.75, 165.75);
}
:global .react-datepicker__navigation-icon {
position: relative;
top: -1px;
font-size: 20px;
width: 0;
}
:global .react-datepicker__navigation-icon--next {
left: -2px;
}
:global .react-datepicker__navigation-icon--next::before {
transform: rotate(45deg);
left: -7px;
}
:global .react-datepicker__navigation-icon--previous {
right: -2px;
}
:global .react-datepicker__navigation-icon--previous::before {
transform: rotate(225deg);
right: -7px;
}
:global .react-datepicker__month-container {
float: left;
}
:global .react-datepicker__year {
margin: 0.5em;
text-align: center;
}
:global .react-datepicker__year-wrapper {
display: flex;
flex-wrap: wrap;
max-width: 180px;
}
:global .react-datepicker__year .react-datepicker__year-text {
display: inline-block;
width: 5em;
margin: 2px;
}
:global .react-datepicker__month {
margin: 0.5em;
text-align: center;
}
:global .react-datepicker__month .react-datepicker__month-text,
:global .react-datepicker__month .react-datepicker__quarter-text {
display: inline-block;
width: 5em;
margin: 2px;
}
:global .react-datepicker__input-time-container {
clear: both;
width: 100%;
float: left;
margin: 5px 0 10px 15px;
text-align: left;
}
:global .react-datepicker__input-time-container .react-datepicker-time__caption {
display: inline-block;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container {
display: inline-block;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input {
display: inline-block;
margin-left: 10px;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input {
width: auto;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-inner-spin-button,
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__input input[type=time] {
-moz-appearance: textfield;
}
:global .react-datepicker__input-time-container .react-datepicker-time__input-container .react-datepicker-time__delimiter {
margin-left: 5px;
display: inline-block;
}
:global .react-datepicker__time-container {
float: right;
border-left: 1px solid #aeaeae;
width: 85px;
}
:global .react-datepicker__time-container--with-today-button {
display: inline;
border: 1px solid #aeaeae;
border-radius: 0.375em;
position: absolute;
right: -87px;
top: 0;
}
:global .react-datepicker__time-container .react-datepicker__time {
position: relative;
background: white;
border-bottom-right-radius: 0.375em;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box {
width: 85px;
overflow-x: hidden;
margin: 0 auto;
text-align: center;
border-bottom-right-radius: 0.375em;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list {
list-style: none;
margin: 0;
height: calc(195px + 2.125em / 2);
overflow-y: scroll;
padding-right: 0;
padding-left: 0;
width: 100%;
box-sizing: content-box;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item {
height: 30px;
padding: 5px 10px;
white-space: nowrap;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover {
cursor: pointer;
background-color: #f0f0f0;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected {
background-color: #216ba5;
color: white;
font-weight: bold;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected:hover {
background-color: #216ba5;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled {
color: #ccc;
}
:global .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled:hover {
cursor: default;
background-color: transparent;
}
:global .react-datepicker__week-number {
color: #ccc;
display: inline-block;
width: 2.125em;
line-height: 2.125em;
text-align: center;
margin: 0.208em;
}
:global .react-datepicker__week-number.react-datepicker__week-number--clickable {
cursor: pointer;
}
:global .react-datepicker__week-number.react-datepicker__week-number--clickable:not(.react-datepicker__week-number--selected):hover {
border-radius: 0.3rem;
background-color: #f0f0f0;
}
:global .react-datepicker__week-number--selected {
border-radius: 0.3rem;
background-color: #216ba5;
color: #fff;
}
:global .react-datepicker__week-number--selected:hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
}
:global .react-datepicker__day-names {
text-align: center;
white-space: nowrap;
margin-bottom: -8px;
}
:global .react-datepicker__week {
white-space: nowrap;
}
:global .react-datepicker__day-name,
:global .react-datepicker__day,
:global .react-datepicker__time-name {
color: #000;
display: inline-block;
width: 2.125em;
line-height: 2.125em;
text-align: center;
margin: 0.208em;
}
:global .react-datepicker__day-name--disabled,
:global .react-datepicker__day--disabled,
:global .react-datepicker__time-name--disabled {
cursor: default;
color: #ccc;
}
:global .react-datepicker__day,
:global .react-datepicker__month-text,
:global .react-datepicker__quarter-text,
:global .react-datepicker__year-text {
cursor: pointer;
}
:global .react-datepicker__day:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text:not([aria-disabled=true]):hover {
border-radius: 0.3rem;
background-color: #f0f0f0;
}
:global .react-datepicker__day--today,
:global .react-datepicker__month-text--today,
:global .react-datepicker__quarter-text--today,
:global .react-datepicker__year-text--today {
font-weight: bold;
}
:global .react-datepicker__day--highlighted,
:global .react-datepicker__month-text--highlighted,
:global .react-datepicker__quarter-text--highlighted,
:global .react-datepicker__year-text--highlighted {
border-radius: 0.3rem;
background-color: #3dcc4a;
color: #fff;
}
:global .react-datepicker__day--highlighted:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--highlighted:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--highlighted:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--highlighted:not([aria-disabled=true]):hover {
background-color: rgb(49.8551020408, 189.6448979592, 62.5632653061);
}
:global .react-datepicker__day--highlighted-custom-1,
:global .react-datepicker__month-text--highlighted-custom-1,
:global .react-datepicker__quarter-text--highlighted-custom-1,
:global .react-datepicker__year-text--highlighted-custom-1 {
color: magenta;
}
:global .react-datepicker__day--highlighted-custom-2,
:global .react-datepicker__month-text--highlighted-custom-2,
:global .react-datepicker__quarter-text--highlighted-custom-2,
:global .react-datepicker__year-text--highlighted-custom-2 {
color: green;
}
:global .react-datepicker__day--holidays,
:global .react-datepicker__month-text--holidays,
:global .react-datepicker__quarter-text--holidays,
:global .react-datepicker__year-text--holidays {
position: relative;
border-radius: 0.3rem;
background-color: #ff6803;
color: #fff;
}
:global .react-datepicker__day--holidays .overlay,
:global .react-datepicker__month-text--holidays .overlay,
:global .react-datepicker__quarter-text--holidays .overlay,
:global .react-datepicker__year-text--holidays .overlay {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;
}
:global .react-datepicker__day--holidays:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--holidays:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--holidays:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--holidays:not([aria-disabled=true]):hover {
background-color: rgb(207, 82.9642857143, 0);
}
:global .react-datepicker__day--holidays:hover .overlay,
:global .react-datepicker__month-text--holidays:hover .overlay,
:global .react-datepicker__quarter-text--holidays:hover .overlay,
:global .react-datepicker__year-text--holidays:hover .overlay {
visibility: visible;
opacity: 1;
}
:global .react-datepicker__day--selected, :global .react-datepicker__day--in-selecting-range, :global .react-datepicker__day--in-range,
:global .react-datepicker__month-text--selected,
:global .react-datepicker__month-text--in-selecting-range,
:global .react-datepicker__month-text--in-range,
:global .react-datepicker__quarter-text--selected,
:global .react-datepicker__quarter-text--in-selecting-range,
:global .react-datepicker__quarter-text--in-range,
:global .react-datepicker__year-text--selected,
:global .react-datepicker__year-text--in-selecting-range,
:global .react-datepicker__year-text--in-range {
border-radius: 0.3rem;
background-color: #216ba5;
color: #fff;
}
:global .react-datepicker__day--selected:not([aria-disabled=true]):hover, :global .react-datepicker__day--in-selecting-range:not([aria-disabled=true]):hover, :global .react-datepicker__day--in-range:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--selected:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--in-selecting-range:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--in-range:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--selected:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--in-selecting-range:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--in-range:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--selected:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--in-selecting-range:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--in-range:not([aria-disabled=true]):hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
}
:global .react-datepicker__day--keyboard-selected,
:global .react-datepicker__month-text--keyboard-selected,
:global .react-datepicker__quarter-text--keyboard-selected,
:global .react-datepicker__year-text--keyboard-selected {
border-radius: 0.3rem;
background-color: rgb(186.25, 217.0833333333, 241.25);
color: rgb(0, 0, 0);
}
:global .react-datepicker__day--keyboard-selected:not([aria-disabled=true]):hover,
:global .react-datepicker__month-text--keyboard-selected:not([aria-disabled=true]):hover,
:global .react-datepicker__quarter-text--keyboard-selected:not([aria-disabled=true]):hover,
:global .react-datepicker__year-text--keyboard-selected:not([aria-disabled=true]):hover {
background-color: rgb(28.75, 93.2196969697, 143.75);
color: #fff;
}
:global .react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
:global .react-datepicker__month-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
:global .react-datepicker__quarter-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
:global .react-datepicker__year-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range) {
background-color: rgba(33, 107, 165, 0.5);
}
:global .react-datepicker__month--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range), :global .react-datepicker__year--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__month--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__year--selecting-range .react-datepicker__month-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__month--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__year--selecting-range .react-datepicker__quarter-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__month--selecting-range .react-datepicker__year-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range),
:global .react-datepicker__year--selecting-range .react-datepicker__year-text--in-range:not(.react-datepicker__day--in-selecting-range,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__year-text--in-selecting-range) {
background-color: #f0f0f0;
color: #000;
}
:global .react-datepicker__day--disabled,
:global .react-datepicker__month-text--disabled,
:global .react-datepicker__quarter-text--disabled,
:global .react-datepicker__year-text--disabled {
cursor: default;
color: #ccc;
}
:global .react-datepicker__day--disabled .overlay,
:global .react-datepicker__month-text--disabled .overlay,
:global .react-datepicker__quarter-text--disabled .overlay,
:global .react-datepicker__year-text--disabled .overlay {
position: absolute;
bottom: 70%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;
}
:global .react-datepicker__input-container {
position: relative;
display: inline-block;
width: 100%;
}
:global .react-datepicker__input-container .react-datepicker__calendar-icon {
position: absolute;
padding: 0.625em;
box-sizing: content-box;
}
:global .react-datepicker__view-calendar-icon input {
padding: 6px 10px 5px 25px;
}
:global .react-datepicker__year-read-view,
:global .react-datepicker__month-read-view,
:global .react-datepicker__month-year-read-view {
border: 1px solid transparent;
border-radius: 0.3rem;
position: relative;
}
:global .react-datepicker__year-read-view:hover,
:global .react-datepicker__month-read-view:hover,
:global .react-datepicker__month-year-read-view:hover {
cursor: pointer;
}
:global .react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__year-read-view:hover .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-read-view:hover .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-read-view:hover .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-year-read-view:hover .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-year-read-view:hover .react-datepicker__month-read-view--down-arrow {
border-top-color: rgb(178.5, 178.5, 178.5);
}
:global .react-datepicker__year-read-view--down-arrow,
:global .react-datepicker__month-read-view--down-arrow,
:global .react-datepicker__month-year-read-view--down-arrow {
transform: rotate(135deg);
right: -16px;
top: 0;
}
:global .react-datepicker__year-dropdown,
:global .react-datepicker__month-dropdown,
:global .react-datepicker__month-year-dropdown {
background-color: #f0f0f0;
position: absolute;
width: 50%;
left: 25%;
top: 30px;
z-index: 1;
text-align: center;
border-radius: 0.3rem;
border: 1px solid #aeaeae;
}
:global .react-datepicker__year-dropdown:hover,
:global .react-datepicker__month-dropdown:hover,
:global .react-datepicker__month-year-dropdown:hover {
cursor: pointer;
}
:global .react-datepicker__year-dropdown--scrollable,
:global .react-datepicker__month-dropdown--scrollable,
:global .react-datepicker__month-year-dropdown--scrollable {
height: 150px;
overflow-y: scroll;
}
:global .react-datepicker__year-option,
:global .react-datepicker__month-option,
:global .react-datepicker__month-year-option {
line-height: 20px;
width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
}
:global .react-datepicker__year-option:first-of-type,
:global .react-datepicker__month-option:first-of-type,
:global .react-datepicker__month-year-option:first-of-type {
border-top-left-radius: 0.3rem;
border-top-right-radius: 0.3rem;
}
:global .react-datepicker__year-option:last-of-type,
:global .react-datepicker__month-option:last-of-type,
:global .react-datepicker__month-year-option:last-of-type {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-bottom-left-radius: 0.3rem;
border-bottom-right-radius: 0.3rem;
}
:global .react-datepicker__year-option:hover,
:global .react-datepicker__month-option:hover,
:global .react-datepicker__month-year-option:hover {
background-color: #ccc;
}
:global .react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming,
:global .react-datepicker__month-option:hover .react-datepicker__navigation--years-upcoming,
:global .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-upcoming {
border-bottom-color: rgb(178.5, 178.5, 178.5);
}
:global .react-datepicker__year-option:hover .react-datepicker__navigation--years-previous,
:global .react-datepicker__month-option:hover .react-datepicker__navigation--years-previous,
:global .react-datepicker__month-year-option:hover .react-datepicker__navigation--years-previous {
border-top-color: rgb(178.5, 178.5, 178.5);
}
:global .react-datepicker__year-option--selected,
:global .react-datepicker__month-option--selected,
:global .react-datepicker__month-year-option--selected {
position: absolute;
left: 15px;
}
:global .react-datepicker__close-icon {
cursor: pointer;
background-color: transparent;
border: 0;
outline: 0;
padding: 0 6px 0 0;
position: absolute;
top: 0;
right: 0;
height: 100%;
display: table-cell;
vertical-align: middle;
}
:global .react-datepicker__close-icon::after {
cursor: pointer;
background-color: #216ba5;
color: #fff;
border-radius: 50%;
height: 16px;
width: 16px;
padding: 2px;
font-size: 12px;
line-height: 1;
text-align: center;
display: table-cell;
vertical-align: middle;
content: "×";
}
:global .react-datepicker__close-icon--disabled {
cursor: default;
}
:global .react-datepicker__close-icon--disabled::after {
cursor: default;
background-color: #ccc;
}
:global .react-datepicker__today-button {
background: #f0f0f0;
border-top: 1px solid #aeaeae;
cursor: pointer;
text-align: center;
font-weight: bold;
padding: 5px 0;
clear: left;
}
:global .react-datepicker__portal {
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.8);
left: 0;
top: 0;
justify-content: center;
align-items: center;
display: flex;
z-index: 2147483647;
}
:global .react-datepicker__children-container {
width: 17.25em;
margin: 0.5em;
padding-right: 0.25em;
padding-left: 0.25em;
height: auto;
}
:global .react-datepicker__aria-live {
position: absolute;
clip-path: circle(0);
border: 0;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
width: 1px;
white-space: nowrap;
}
:global .react-datepicker__calendar-icon {
width: 1em;
height: 1em;
vertical-align: -0.125em;
}
:global .react-datepicker-popper-offset {
margin-top: -0.7em;
}
+45
View File
@@ -0,0 +1,45 @@
import React, { Component } from "react";
import type { ReactNode } from "react";
interface TabLoopProps {
enableTabLoop?: boolean;
children?: ReactNode | undefined;
}
/**
* `TabLoop` is a React component that manages tabbing behavior for its children.
*
* TabLoop prevents the user from tabbing outside of the popper
* It creates a tabindex loop so that "Tab" on the last element will focus the first element
* and "Shift Tab" on the first element will focus the last element
*
* @component
* @example
* <TabLoop enableTabLoop={true}>
* <ChildComponent />
* </TabLoop>
*
* @param props - The properties that define the `TabLoop` component.
* @param props.children - The child components.
* @param props.enableTabLoop - Whether to enable the tab loop.
*
* @returns The `TabLoop` component.
*/
export default class TabLoop extends Component<TabLoopProps> {
static defaultProps: {
enableTabLoop: boolean;
};
constructor(props: TabLoopProps);
private tabLoopRef;
/**
* `getTabChildren` is a method of the `TabLoop` class that retrieves all tabbable children of the component.
*
* This method uses the `tabbable` library to find all tabbable elements within the `TabLoop` component.
* It then filters out any elements that are not visible.
*
* @returns An array of all tabbable and visible children of the `TabLoop` component.
*/
getTabChildren: () => any[];
handleFocusStart: () => void;
handleFocusEnd: () => void;
render(): React.ReactNode;
}
export {};
+49
View File
@@ -0,0 +1,49 @@
import React, { Component } from "react";
import { type Locale, type TimeFilterOptions } from "./date_utils";
interface TimeProps extends Pick<TimeFilterOptions, "minTime" | "maxTime" | "excludeTimes" | "includeTimes" | "filterTime"> {
format?: string;
intervals?: number;
selected?: Date | null;
openToDate?: Date;
onChange?: (time: Date) => void;
timeClassName?: (time: Date) => string;
todayButton?: React.ReactNode;
monthRef?: HTMLDivElement;
timeCaption?: string;
injectTimes?: Date[];
handleOnKeyDown?: React.KeyboardEventHandler<HTMLLIElement>;
locale?: Locale;
showTimeSelectOnly?: boolean;
showTimeCaption?: boolean;
}
interface TimeState {
height: number | null;
}
export default class Time extends Component<TimeProps, TimeState> {
static get defaultProps(): {
intervals: number;
todayButton: null;
timeCaption: string;
showTimeCaption: boolean;
};
static calcCenterPosition: (listHeight: number, centerLiRef: HTMLLIElement) => number;
private resizeObserver?;
state: TimeState;
componentDidMount(): void;
componentWillUnmount(): void;
private header?;
private list?;
private centerLi?;
private observeDatePickerHeightChanges;
private updateContainerHeight;
scrollToTheSelectedTime: () => void;
handleClick: (time: Date) => void;
isSelectedTime: (time: Date) => boolean | null;
isDisabledTime: (time: Date) => boolean | undefined;
liClasses: (time: Date) => string;
handleOnKeyDown: (event: React.KeyboardEvent<HTMLLIElement>, time: Date) => void;
renderTimes: () => React.ReactElement[];
renderTimeCaption: () => React.ReactElement;
render(): React.JSX.Element;
}
export {};
+35
View File
@@ -0,0 +1,35 @@
import React, { Component } from "react";
import Day from "./day";
import WeekNumber from "./week_number";
interface DayProps extends React.ComponentPropsWithoutRef<typeof Day> {
}
interface WeekNumberProps extends React.ComponentPropsWithoutRef<typeof WeekNumber> {
}
interface WeekProps extends Omit<DayProps, "ariaLabelPrefixWhenEnabled" | "ariaLabelPrefixWhenDisabled" | "day" | "onClick" | "onMouseEnter">, Omit<WeekNumberProps, "weekNumber" | "date" | "onClick"> {
day: Date;
chooseDayAriaLabelPrefix?: DayProps["ariaLabelPrefixWhenEnabled"];
disabledDayAriaLabelPrefix?: DayProps["ariaLabelPrefixWhenDisabled"];
onDayClick?: (day: Date, event: React.MouseEvent<HTMLDivElement>) => void;
onDayMouseEnter?: (day: Date) => void;
shouldCloseOnSelect?: boolean;
setOpen?: (open: boolean) => void;
formatWeekNumber?: (date: Date) => number;
onWeekSelect?: (day: Date, weekNumber: number, event: React.MouseEvent<HTMLDivElement>) => void;
weekClassName?: (date: Date) => string;
}
export default class Week extends Component<WeekProps> {
static get defaultProps(): {
shouldCloseOnSelect: boolean;
};
isDisabled: (day: Date) => boolean;
handleDayClick: (day: Date, event: React.MouseEvent<HTMLDivElement>) => void;
handleDayMouseEnter: (day: Date) => void;
handleWeekClick: (day: Date, weekNumber: number, event: React.MouseEvent<HTMLDivElement>) => void;
formatWeekNumber: (date: Date) => number;
isWeekDisabled: () => boolean;
renderDays: () => React.JSX.Element[];
startOfWeek: () => Date;
isKeyboardSelected: () => boolean;
render(): React.ReactElement;
}
export {};
+33
View File
@@ -0,0 +1,33 @@
import React, { Component } from "react";
interface WeekNumberProps {
weekNumber: number;
date: Date;
onClick?: React.MouseEventHandler<HTMLDivElement>;
ariaLabelPrefix?: string;
selected?: Date | null;
preSelection?: Date | null;
showWeekPicker?: boolean;
showWeekNumber?: boolean;
disabledKeyboardNavigation?: boolean;
inline?: boolean;
shouldFocusDayInline?: boolean;
handleOnKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
containerRef?: React.RefObject<HTMLDivElement | null>;
isInputFocused?: boolean;
isWeekDisabled?: boolean;
}
export default class WeekNumber extends Component<WeekNumberProps> {
static get defaultProps(): {
ariaLabelPrefix: string;
};
componentDidMount(): void;
componentDidUpdate(prevProps: WeekNumberProps): void;
weekNumberEl: React.RefObject<HTMLDivElement | null>;
handleClick: (event: React.MouseEvent<HTMLDivElement>) => void;
handleOnKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
isKeyboardSelected: () => boolean;
getTabIndex: () => number;
handleFocusWeekNumber: (prevProps?: Partial<WeekNumberProps>) => void;
render(): React.ReactElement;
}
export {};
+34
View File
@@ -0,0 +1,34 @@
import { type UseFloatingOptions, type Middleware, type Placement, type UseFloatingReturn } from "@floating-ui/react";
import React from "react";
export interface FloatingProps {
hidePopper?: boolean;
popperProps: UseFloatingReturn & {
arrowRef: React.RefObject<SVGSVGElement>;
};
}
export interface WithFloatingProps {
popperModifiers?: Middleware[];
popperProps?: Omit<UseFloatingOptions, "middleware">;
hidePopper?: boolean;
popperPlacement?: Placement;
}
/**
* `withFloating` is a higher-order component that adds floating behavior to a component.
*
* @param Component - The component to enhance.
*
* @example
* const FloatingComponent = withFloating(MyComponent);
* <FloatingComponent popperModifiers={[]} popperProps={{}} hidePopper={true} />
*
* @param popperModifiers - The modifiers to use for the popper.
* @param popperProps - The props to pass to the popper.
* @param hidePopper - Whether to hide the popper.
* @param popperPlacement - The placement of the popper.
*
* @returns A new component with floating behavior.
*/
export default function withFloating<T extends FloatingProps>(Component: React.ComponentType<T>): {
(props: Omit<T, "popperProps"> & WithFloatingProps): React.ReactElement;
displayName: string;
};
+74
View File
@@ -0,0 +1,74 @@
import React, { Component } from "react";
import { type DateFilterOptionsWithDisabled } from "./date_utils";
interface YearProps extends Pick<DateFilterOptionsWithDisabled, "minDate" | "maxDate" | "excludeDates" | "includeDates" | "filterDate" | "disabled"> {
clearSelectingDate?: VoidFunction;
date?: Date;
disabledKeyboardNavigation?: boolean;
onDayClick?: (date: Date, event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
preSelection?: Date | null;
setPreSelection?: (date?: Date | null) => void;
selectsMultiple?: boolean;
selectedDates?: Date[];
selected?: Date | null;
inline?: boolean;
usePointerEvent?: boolean;
onYearMouseEnter: (event: React.MouseEvent<HTMLDivElement, MouseEvent>, year: number) => void;
onYearMouseLeave: (event: React.MouseEvent<HTMLDivElement, MouseEvent>, year: number) => void;
selectingDate?: Date;
renderYearContent?: (year: number) => React.ReactNode;
selectsEnd?: boolean;
selectsStart?: boolean;
selectsRange?: boolean;
startDate?: Date | null;
endDate?: Date | null;
yearItemNumber?: number;
handleOnKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
yearClassName?: (date: Date) => string;
}
/**
* `Year` is a component that represents a year in a date picker.
*
* @class
* @param {YearProps} props - The properties that define the `Year` component.
* @property {VoidFunction} [props.clearSelectingDate] - Function to clear the selected date.
* @property {Date} [props.date] - The currently selected date.
* @property {boolean} [props.disabledKeyboardNavigation] - If true, keyboard navigation is disabled.
* @property {Date} [props.endDate] - The end date in a range selection.
* @property {(date: Date) => void} props.onDayClick - Function to handle day click events.
* @property {Date} props.preSelection - The date that is currently in focus.
* @property {(date: Date) => void} props.setPreSelection - Function to set the pre-selected date.
* @property {{ [key: string]: any }} props.selected - The selected date(s).
* @property {boolean} props.inline - If true, the date picker is displayed inline.
* @property {Date} props.maxDate - The maximum selectable date.
* @property {Date} props.minDate - The minimum selectable date.
* @property {boolean} props.usePointerEvent - If true, pointer events are used instead of mouse events.
* @property {(date: Date) => void} props.onYearMouseEnter - Function to handle mouse enter events on a year.
* @property {(date: Date) => void} props.onYearMouseLeave - Function to handle mouse leave events on a year.
*/
export default class Year extends Component<YearProps> {
constructor(props: YearProps);
YEAR_REFS: React.RefObject<HTMLDivElement | null>[];
isDisabled: (date: Date) => boolean;
isExcluded: (date: Date) => boolean;
selectingDate: () => Date | null | undefined;
updateFocusOnPaginate: (refIndex: number) => void;
handleYearClick: (day: Date, event: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void;
handleYearNavigation: (newYear: number, newDate: Date) => void;
isSameDay: (y: Date, other: Date) => boolean;
isCurrentYear: (y: number) => boolean;
isRangeStart: (y: number) => boolean | null | undefined;
isRangeEnd: (y: number) => boolean | null | undefined;
isInRange: (y: number) => boolean;
isInSelectingRange: (y: number) => boolean;
isSelectingRangeStart: (y: number) => boolean;
isSelectingRangeEnd: (y: number) => boolean;
isKeyboardSelected: (y: number) => boolean | undefined;
isSelectedYear: (year: number) => boolean | undefined;
onYearClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.KeyboardEvent<HTMLDivElement>, y: number) => void;
onYearKeyDown: (event: React.KeyboardEvent<HTMLDivElement>, y: number) => void;
getYearClassNames: (y: number) => string;
getYearTabIndex: (y: number) => "-1" | "0";
getYearContent: (y: number) => React.ReactNode;
render(): React.JSX.Element | null;
}
export {};
+31
View File
@@ -0,0 +1,31 @@
import React, { Component } from "react";
import YearDropdownOptions from "./year_dropdown_options";
interface YearDropdownOptionsProps extends React.ComponentPropsWithoutRef<typeof YearDropdownOptions> {
}
interface YearDropdownProps extends Omit<YearDropdownOptionsProps, "onChange" | "onCancel"> {
adjustDateOnChange?: boolean;
dropdownMode: "scroll" | "select";
onChange: (year: number) => void;
date: Date;
onSelect?: (date: Date, event?: React.MouseEvent<HTMLButtonElement>) => void;
setOpen?: (open: boolean) => void;
}
interface YearDropdownState {
dropdownVisible: boolean;
}
export default class YearDropdown extends Component<YearDropdownProps, YearDropdownState> {
state: YearDropdownState;
renderSelectOptions: () => React.ReactElement[];
onSelectChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
renderSelectMode: () => React.ReactElement;
renderReadView: (visible: boolean) => React.ReactElement;
renderDropdown: () => React.ReactElement;
renderScrollMode: () => React.ReactElement[];
onChange: (year: number) => void;
toggleDropdown: (event?: React.MouseEvent<HTMLButtonElement>) => void;
handleYearChange: (date: Date, event?: React.MouseEvent<HTMLButtonElement>) => void;
onSelect: (date: Date, event?: React.MouseEvent<HTMLButtonElement>) => void;
setOpen: () => void;
render(): React.ReactElement;
}
export {};
+28
View File
@@ -0,0 +1,28 @@
import React, { Component } from "react";
interface YearDropdownOptionsProps {
minDate?: Date;
maxDate?: Date;
onChange: (year: number) => void;
onCancel: VoidFunction;
scrollableYearDropdown?: boolean;
year: number;
yearDropdownItemNumber?: number;
}
interface YearDropdownOptionsState {
yearsList: number[];
}
export default class YearDropdownOptions extends Component<YearDropdownOptionsProps, YearDropdownOptionsState> {
constructor(props: YearDropdownOptionsProps);
componentDidMount(): void;
dropdownRef: React.RefObject<HTMLDivElement | null>;
yearOptionButtonsRef: Record<number, HTMLDivElement | null>;
handleOptionKeyDown: (year: number, e: React.KeyboardEvent) => void;
renderOptions: () => React.ReactElement[];
onChange: (year: number) => void;
handleClickOutside: () => void;
shiftYears: (amount: number) => void;
incrementYears: () => void;
decrementYears: () => void;
render(): React.JSX.Element;
}
export {};
+141
View File
@@ -0,0 +1,141 @@
{
"author": "HackerOne",
"name": "react-datepicker",
"description": "A simple and reusable datepicker component for React",
"version": "9.1.0",
"license": "MIT",
"homepage": "https://github.com/Hacker0x01/react-datepicker",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"module": "dist/index.es.js",
"unpkg": "dist/react-datepicker.min.js",
"style": "dist/react-datepicker.min.css",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.es.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./dist/": "./dist/",
"./src/stylesheets/": "./src/stylesheets/"
},
"files": ["*.md", "dist", "lib", "es", "src"],
"sideEffects": ["**/*.css"],
"keywords": ["react", "datepicker", "calendar", "date", "react-component"],
"repository": {
"type": "git",
"url": "git://github.com/Hacker0x01/react-datepicker.git"
},
"bugs": {
"url": "https://github.com/Hacker0x01/react-datepicker/issues"
},
"devDependencies": {
"@babel/core": "^7.26.7",
"@babel/eslint-parser": "^7.26.5",
"@babel/helpers": "^7.26.7",
"@babel/plugin-external-helpers": "^7.25.9",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.26.7",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@eslint/js": "^9.19.0",
"@react-docgen/cli": "^3.0.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "14.6.1",
"@types/eslint": "^9.6.1",
"@types/jest": "^30.0.0",
"@types/jest-axe": "^3.5.9",
"@types/node": "25.0.3",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.1.7",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"axe-core": "^4.10.2",
"babel-jest": "^30.0.2",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"core-js": "^3.46.0",
"date-fns-tz": "^3.2.0",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-unused-imports": "^4.1.4",
"husky": "9.1.7",
"jest": "^30.0.5",
"jest-axe": "^10.0.0",
"jest-canvas-mock": "^2.5.2",
"jest-environment-jsdom": "^30.1.2",
"lint-staged": "^16.0.0",
"lodash": "^4.17.21",
"prettier": "^3.4.2",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"rollup": "^4.32.1",
"rollup-plugin-filesize": "^10.0.0",
"sass": "1.97.0",
"slugify": "^1.6.6",
"stylelint": "^16.23.0",
"stylelint-config-standard": "^39.0.0",
"stylelint-config-standard-scss": "^16.0.0",
"stylelint-scss": "^6.11.0",
"ts-jest": "^29.2.5",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.22.0"
},
"peerDependencies": {
"date-fns-tz": "^3.0.0",
"react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"date-fns-tz": {
"optional": true
}
},
"dependencies": {
"@floating-ui/react": "^0.27.15",
"clsx": "^2.1.1",
"date-fns": "^4.1.0"
},
"scripts": {
"eslint": "eslint ./src",
"precommit": "lint-staged --allow-empty",
"sass-lint": "stylelint 'src/stylesheets/*.scss'",
"lint": "yarn run eslint && yarn run sass-lint",
"prettier": "prettier --write '**/*.{js,jsx,ts,tsx}'",
"prettier:check": "prettier --check '**/*.{js,jsx,ts,tsx}'",
"start": "yarn --cwd docs-site install && yarn --cwd docs-site start",
"test": "NODE_ENV=test jest",
"test:ci": "NODE_ENV=test jest --ci --coverage",
"test:watch": "NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=test jest --watch",
"build": "NODE_ENV=production yarn run build:src && NODE_ENV=production yarn run css:prod && NODE_ENV=production yarn run css:modules:prod && NODE_ENV=production yarn run css:dev && NODE_ENV=production yarn run css:modules:dev",
"build-dev": "NODE_ENV=development yarn run js:dev && NODE_ENV=development yarn run css:dev && NODE_ENV=development yarn run css:modules:dev",
"css:prod": "sass --style compressed src/stylesheets/datepicker.scss > dist/react-datepicker.min.css",
"css:modules:prod": "sass --style compressed src/stylesheets/datepicker-cssmodules.scss | tee dist/react-datepicker-cssmodules.min.css dist/react-datepicker-min.module.css",
"css:dev": "sass --style expanded src/stylesheets/datepicker.scss > dist/react-datepicker.css",
"css:modules:dev": "sass --style expanded src/stylesheets/datepicker-cssmodules.scss | tee dist/react-datepicker-cssmodules.css dist/react-datepicker.module.css",
"type-check": "tsc --project tsconfig.build.json --noEmit",
"type-check:watch": "npm run type-check -- --watch",
"build:src": "rollup -c",
"js:dev": "rollup -cw",
"prepare": "husky"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,scss,md}": ["prettier --write", "git add"]
},
"packageManager": "yarn@4.9.2"
}
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
import React, { type HTMLAttributes } from "react";
export interface CalendarContainerProps extends React.PropsWithChildren<
HTMLAttributes<HTMLDivElement>
> {
showTimeSelectOnly?: boolean;
showTime?: boolean;
inline?: boolean;
}
const CalendarContainer: React.FC<CalendarContainerProps> = function ({
showTimeSelectOnly = false,
showTime = false,
className,
children,
inline,
}: CalendarContainerProps) {
const ariaLabel = showTimeSelectOnly
? "Choose Time"
: `Choose Date${showTime ? " and Time" : ""}`;
return (
<div
className={className}
aria-label={ariaLabel}
role={inline ? undefined : "dialog"}
aria-modal={inline ? undefined : "true"}
translate="no"
>
{children}
</div>
);
};
export default CalendarContainer;
+79
View File
@@ -0,0 +1,79 @@
import React from "react";
interface CalendarIconProps {
icon?: string | React.ReactNode;
className?: string;
onClick?: (event: React.MouseEvent) => void;
}
/**
* `CalendarIcon` is a React component that renders an icon for a calendar.
* The icon can be a string representing a CSS class, a React node, or a default SVG icon.
*
* @component
* @prop icon - The icon to be displayed. This can be a string representing a CSS class or a React node.
* @prop className - An optional string representing additional CSS classes to be applied to the icon.
* @prop onClick - An optional function to be called when the icon is clicked.
*
* @example
* // To use a CSS class as the icon
* <CalendarIcon icon="my-icon-class" onClick={myClickHandler} />
*
* @example
* // To use a React node as the icon
* <CalendarIcon icon={<MyIconComponent />} onClick={myClickHandler} />
*
* @returns The `CalendarIcon` component.
*/
const CalendarIcon: React.FC<CalendarIconProps> = ({
icon,
className = "",
onClick,
}: CalendarIconProps): React.ReactElement => {
const defaultClass = "react-datepicker__calendar-icon";
if (typeof icon === "string") {
return (
<i
className={`${defaultClass} ${icon} ${className}`}
aria-hidden="true"
onClick={onClick}
/>
);
}
if (React.isValidElement(icon)) {
// Because we are checking that typeof icon is string first, we can safely cast icon as React.ReactElement on types level and code level
const iconElement = icon as React.ReactElement<{
className: string;
onClick: (event: React.MouseEvent) => void;
}>;
return React.cloneElement(iconElement, {
className: `${iconElement.props.className || ""} ${defaultClass} ${className}`,
onClick: (event: React.MouseEvent) => {
if (typeof iconElement.props.onClick === "function") {
iconElement.props.onClick(event);
}
if (typeof onClick === "function") {
onClick(event);
}
},
});
}
// Default SVG Icon
return (
<svg
className={`${defaultClass} ${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
onClick={onClick}
>
<path d="M96 32V64H48C21.5 64 0 85.5 0 112v48H448V112c0-26.5-21.5-48-48-48H352V32c0-17.7-14.3-32-32-32s-32 14.3-32 32V64H160V32c0-17.7-14.3-32-32-32S96 14.3 96 32zM448 192H0V464c0 26.5 21.5 48 48 48H400c26.5 0 48-21.5 48-48V192z" />
</svg>
);
};
export default CalendarIcon;
+78
View File
@@ -0,0 +1,78 @@
import React, { useCallback, useEffect, useRef } from "react";
export type ClickOutsideHandler = (event: MouseEvent) => void;
interface ClickOutsideWrapperProps {
onClickOutside: ClickOutsideHandler;
className?: string;
children: React.ReactNode;
containerRef?: React.RefObject<HTMLDivElement | null>;
style?: React.CSSProperties;
ignoreClass?: string;
}
const useDetectClickOutside = (
onClickOutside: ClickOutsideHandler,
ignoreClass?: string,
) => {
const ref = useRef<HTMLDivElement | null>(null);
const onClickOutsideRef = useRef(onClickOutside);
useEffect(() => {
onClickOutsideRef.current = onClickOutside;
}, [onClickOutside]);
const handleClickOutside = useCallback(
(event: MouseEvent) => {
const target =
(event.composed &&
event.composedPath &&
event
.composedPath()
.find((eventTarget) => eventTarget instanceof Node)) ||
event.target;
if (ref.current && !ref.current.contains(target as Node)) {
if (
!(
ignoreClass &&
target instanceof HTMLElement &&
target.classList.contains(ignoreClass)
)
) {
onClickOutsideRef.current?.(event);
}
}
},
[ignoreClass],
);
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [handleClickOutside]);
return ref;
};
export const ClickOutsideWrapper: React.FC<ClickOutsideWrapperProps> = ({
children,
onClickOutside,
className,
containerRef,
style,
ignoreClass,
}) => {
const detectRef = useDetectClickOutside(onClickOutside, ignoreClass);
return (
<div
className={className}
style={style}
ref={(node: HTMLDivElement | null) => {
detectRef.current = node;
if (containerRef) {
containerRef.current = node;
}
}}
>
{children}
</div>
);
};
File diff suppressed because it is too large Load Diff
+618
View File
@@ -0,0 +1,618 @@
import { clsx } from "clsx";
import React, { Component, createRef } from "react";
import {
getDay,
getMonth,
getDate,
newDate,
isSameDay,
isDayDisabled,
isDayExcluded,
isDayInRange,
isEqual,
isBefore,
isAfter,
getDayOfWeekCode,
getStartOfWeek,
formatDate,
type DateFilterOptionsWithDisabled,
type DateNumberType,
type Locale,
type HolidaysMap,
KeyType,
} from "./date_utils";
interface DayProps extends Pick<
DateFilterOptionsWithDisabled,
| "minDate"
| "maxDate"
| "excludeDates"
| "excludeDateIntervals"
| "includeDateIntervals"
| "includeDates"
| "filterDate"
| "disabled"
> {
ariaLabelPrefixWhenEnabled?: string;
ariaLabelPrefixWhenDisabled?: string;
disabledKeyboardNavigation?: boolean;
day: Date;
dayClassName?: (date: Date) => string;
highlightDates?: Map<string, string[]>;
holidays?: HolidaysMap;
inline?: boolean;
shouldFocusDayInline?: boolean;
month: number;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
handleOnKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
usePointerEvent?: boolean;
preSelection?: Date | null;
selected?: Date | null;
selectingDate?: Date;
selectsEnd?: boolean;
selectsStart?: boolean;
selectsRange?: boolean;
showWeekPicker?: boolean;
showWeekNumber?: boolean;
selectsDisabledDaysInRange?: boolean;
selectsMultiple?: boolean;
selectedDates?: Date[];
startDate?: Date | null;
endDate?: Date | null;
renderDayContents?: (day: number, date: Date) => React.ReactNode;
containerRef?: React.RefObject<HTMLDivElement | null>;
calendarStartDay?: DateNumberType;
locale?: Locale;
monthShowsDuplicateDaysEnd?: boolean;
monthShowsDuplicateDaysStart?: boolean;
swapRange?: boolean;
}
/**
* `Day` is a React component that represents a single day in a date picker.
* It handles the rendering and interaction of a day.
*
* @prop ariaLabelPrefixWhenEnabled - Aria label prefix when the day is enabled.
* @prop ariaLabelPrefixWhenDisabled - Aria label prefix when the day is disabled.
* @prop disabledKeyboardNavigation - Whether keyboard navigation is disabled.
* @prop day - The day to be displayed.
* @prop dayClassName - Function to customize the CSS class of the day.
* @prop endDate - The end date in a range.
* @prop highlightDates - Map of dates to be highlighted.
* @prop holidays - Map of holiday dates.
* @prop inline - Whether the date picker is inline.
* @prop shouldFocusDayInline - Whether the day should be focused when date picker is inline.
* @prop month - The month the day belongs to.
* @prop onClick - Click event handler.
* @prop onMouseEnter - Mouse enter event handler.
* @prop handleOnKeyDown - Key down event handler.
* @prop usePointerEvent - Whether to use pointer events.
* @prop preSelection - The date that is currently selected.
* @prop selected - The selected date.
* @prop selectingDate - The date currently being selected.
* @prop selectsEnd - Whether the day can be the end date in a range.
* @prop selectsStart - Whether the day can be the start date in a range.
* @prop selectsRange - Whether the day can be in a range.
* @prop showWeekPicker - Whether to show week picker.
* @prop showWeekNumber - Whether to show week numbers.
* @prop selectsDisabledDaysInRange - Whether to select disabled days in a range.
* @prop selectsMultiple - Whether to allow multiple date selection.
* @prop selectedDates - Array of selected dates.
* @prop startDate - The start date in a range.
* @prop renderDayContents - Function to customize the rendering of the day's contents.
* @prop containerRef - Ref for the container.
* @prop excludeDates - Array of dates to be excluded.
* @prop calendarStartDay - The start day of the week.
* @prop locale - The locale object.
* @prop monthShowsDuplicateDaysEnd - Whether to show duplicate days at the end of the month.
* @prop monthShowsDuplicateDaysStart - Whether to show duplicate days at the start of the month.
* @prop includeDates - Array of dates to be included.
* @prop includeDateIntervals - Array of date intervals to be included.
* @prop minDate - The minimum date that can be selected.
* @prop maxDate - The maximum date that can be selected.
*
* @example
* ```tsx
* import React from 'react';
* import Day from './day';
*
* function MyComponent() {
* const handleDayClick = (event) => {
* console.log('Day clicked', event);
* };
*
* const handleDayMouseEnter = (event) => {
* console.log('Mouse entered day', event);
* };
*
* const renderDayContents = (date) => {
* return <div>{date.getDate()}</div>;
* };
*
* return (
* <Day
* day={new Date()}
* onClick={handleDayClick}
* onMouseEnter={handleDayMouseEnter}
* renderDayContents={renderDayContents}
* />
* );
* }
*
* export default MyComponent;
* ```
*/
export default class Day extends Component<DayProps> {
componentDidMount() {
this.handleFocusDay();
}
componentDidUpdate() {
this.handleFocusDay();
}
dayEl = createRef<HTMLDivElement>();
handleClick: DayProps["onClick"] = (event) => {
if (!this.isDisabled() && this.props.onClick) {
this.props.onClick(event);
}
};
handleMouseEnter: DayProps["onMouseEnter"] = (event) => {
if (!this.isDisabled() && this.props.onMouseEnter) {
this.props.onMouseEnter(event);
}
};
handleOnKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
const eventKey = event.key;
if (eventKey === KeyType.Space) {
event.preventDefault();
event.key = KeyType.Enter;
}
this.props.handleOnKeyDown?.(event);
};
isSameDay = (other: Date | null | undefined) =>
isSameDay(this.props.day, other);
isKeyboardSelected = () => {
if (this.props.disabledKeyboardNavigation) {
return false;
}
const isSelectedDate = this.props.selectsMultiple
? this.props.selectedDates?.some((date) => this.isSameDayOrWeek(date))
: this.isSameDayOrWeek(this.props.selected);
const isDisabled =
this.props.preSelection && this.isDisabled(this.props.preSelection);
return (
!isSelectedDate &&
this.isSameDayOrWeek(this.props.preSelection) &&
!isDisabled
);
};
isDisabled = (day = this.props.day) =>
// Almost all props previously were passed as this.props w/o proper typing with prop-types
// after the migration to TS i made it explicit
isDayDisabled(day, {
minDate: this.props.minDate,
maxDate: this.props.maxDate,
excludeDates: this.props.excludeDates,
excludeDateIntervals: this.props.excludeDateIntervals,
includeDateIntervals: this.props.includeDateIntervals,
includeDates: this.props.includeDates,
filterDate: this.props.filterDate,
disabled: this.props.disabled,
});
isExcluded = () =>
// Almost all props previously were passed as this.props w/o proper typing with prop-types
// after the migration to TS i made it explicit
isDayExcluded(this.props.day, {
excludeDates: this.props.excludeDates,
excludeDateIntervals: this.props.excludeDateIntervals,
});
isStartOfWeek = () =>
isSameDay(
this.props.day,
getStartOfWeek(
this.props.day,
this.props.locale,
this.props.calendarStartDay,
),
);
isSameWeek = (other?: Date | null) =>
this.props.showWeekPicker &&
isSameDay(
other,
getStartOfWeek(
this.props.day,
this.props.locale,
this.props.calendarStartDay,
),
);
isSameDayOrWeek = (other?: Date | null) =>
this.isSameDay(other) || this.isSameWeek(other);
getHighLightedClass = () => {
const { day, highlightDates } = this.props;
if (!highlightDates) {
return false;
}
// Looking for className in the Map of {'day string, 'className'}
const dayStr = formatDate(day, "MM.dd.yyyy");
return highlightDates.get(dayStr);
};
// Function to return the array containing className associated to the date
getHolidaysClass = () => {
const { day, holidays } = this.props;
if (!holidays) {
// For type consistency no other reasons
return [undefined];
}
const dayStr = formatDate(day, "MM.dd.yyyy");
// Looking for className in the Map of {day string: {className, holidayName}}
if (holidays.has(dayStr)) {
return [holidays.get(dayStr)?.className];
}
// For type consistency no other reasons
return [undefined];
};
isInRange = () => {
const { day, startDate, endDate } = this.props;
if (!startDate || !endDate) {
return false;
}
return isDayInRange(day, startDate, endDate);
};
isInSelectingRange = () => {
const {
day,
selectsStart,
selectsEnd,
selectsRange,
selectsDisabledDaysInRange,
startDate,
swapRange,
endDate,
} = this.props;
const selectingDate = this.props.selectingDate ?? this.props.preSelection;
// Don't highlight days outside the current month
if (this.isAfterMonth() || this.isBeforeMonth()) {
return false;
}
if (
!(selectsStart || selectsEnd || selectsRange) ||
!selectingDate ||
(!selectsDisabledDaysInRange && this.isDisabled())
) {
return false;
}
if (
selectsStart &&
endDate &&
(isBefore(selectingDate, endDate) || isEqual(selectingDate, endDate))
) {
return isDayInRange(day, selectingDate, endDate);
}
if (
selectsEnd &&
startDate &&
(isAfter(selectingDate, startDate) || isEqual(selectingDate, startDate))
) {
return isDayInRange(day, startDate, selectingDate);
}
if (selectsRange && startDate && !endDate) {
if (isEqual(selectingDate, startDate)) {
return isDayInRange(day, startDate, selectingDate);
}
if (isAfter(selectingDate, startDate)) {
return isDayInRange(day, startDate, selectingDate);
}
if (swapRange && isBefore(selectingDate, startDate)) {
return isDayInRange(day, selectingDate, startDate);
}
}
return false;
};
isSelectingRangeStart = () => {
if (!this.isInSelectingRange()) {
return false;
}
const { day, startDate, selectsStart, swapRange, selectsRange } =
this.props;
const selectingDate = this.props.selectingDate ?? this.props.preSelection;
if (selectsStart) {
return isSameDay(day, selectingDate);
}
if (selectsRange && swapRange && startDate && selectingDate) {
return isSameDay(
day,
isBefore(selectingDate, startDate) ? selectingDate : startDate,
);
}
return isSameDay(day, startDate);
};
isSelectingRangeEnd = () => {
if (!this.isInSelectingRange()) {
return false;
}
const { day, endDate, selectsEnd, selectsRange, swapRange, startDate } =
this.props;
const selectingDate = this.props.selectingDate ?? this.props.preSelection;
if (selectsEnd) {
return isSameDay(day, selectingDate);
}
if (selectsRange && swapRange && startDate && selectingDate) {
return isSameDay(
day,
isBefore(selectingDate, startDate) ? startDate : selectingDate,
);
}
if (selectsRange) {
return isSameDay(day, selectingDate);
}
return isSameDay(day, endDate);
};
isRangeStart = () => {
const { day, startDate, endDate } = this.props;
if (!startDate || !endDate) {
return false;
}
return isSameDay(startDate, day);
};
isRangeEnd = () => {
const { day, startDate, endDate } = this.props;
if (!startDate || !endDate) {
return false;
}
return isSameDay(endDate, day);
};
isWeekend = () => {
const weekday = getDay(this.props.day);
return weekday === 0 || weekday === 6;
};
isAfterMonth = () => {
return (
this.props.month !== undefined &&
(this.props.month + 1) % 12 === getMonth(this.props.day)
);
};
isBeforeMonth = () => {
return (
this.props.month !== undefined &&
(getMonth(this.props.day) + 1) % 12 === this.props.month
);
};
isCurrentDay = () => this.isSameDay(newDate());
isSelected = () => {
if (this.props.selectsMultiple) {
return this.props.selectedDates?.some((date) =>
this.isSameDayOrWeek(date),
);
}
return this.isSameDayOrWeek(this.props.selected);
};
getClassNames = (date: Date) => {
const dayClassName = this.props.dayClassName
? this.props.dayClassName(date)
: undefined;
return clsx(
"react-datepicker__day",
dayClassName,
"react-datepicker__day--" + getDayOfWeekCode(this.props.day),
{
"react-datepicker__day--disabled": this.isDisabled(),
"react-datepicker__day--excluded": this.isExcluded(),
"react-datepicker__day--selected": this.isSelected(),
"react-datepicker__day--keyboard-selected": this.isKeyboardSelected(),
"react-datepicker__day--range-start": this.isRangeStart(),
"react-datepicker__day--range-end": this.isRangeEnd(),
"react-datepicker__day--in-range": this.isInRange(),
"react-datepicker__day--in-selecting-range": this.isInSelectingRange(),
"react-datepicker__day--selecting-range-start":
this.isSelectingRangeStart(),
"react-datepicker__day--selecting-range-end":
this.isSelectingRangeEnd(),
"react-datepicker__day--today": this.isCurrentDay(),
"react-datepicker__day--weekend": this.isWeekend(),
"react-datepicker__day--outside-month":
this.isAfterMonth() || this.isBeforeMonth(),
},
this.getHighLightedClass(),
this.getHolidaysClass(),
);
};
getAriaLabel = () => {
const {
day,
ariaLabelPrefixWhenEnabled = "Choose",
ariaLabelPrefixWhenDisabled = "Not available",
} = this.props;
const prefix =
this.isDisabled() || this.isExcluded()
? ariaLabelPrefixWhenDisabled
: ariaLabelPrefixWhenEnabled;
return `${prefix} ${formatDate(day, "PPPP", this.props.locale)}`;
};
// A function to return the holiday's name as title's content
getTitle = () => {
const { day, holidays = new Map(), excludeDates } = this.props;
const compareDt = formatDate(day, "MM.dd.yyyy");
const titles = [];
if (holidays.has(compareDt)) {
titles.push(...holidays.get(compareDt).holidayNames);
}
if (this.isExcluded()) {
titles.push(
excludeDates
?.filter((excludeDate) => {
if (excludeDate instanceof Date) {
return isSameDay(excludeDate, day);
}
return isSameDay(excludeDate?.date, day);
})
.map((excludeDate) => {
if (excludeDate instanceof Date) {
return undefined;
}
return excludeDate?.message;
}),
);
}
// I'm not sure that this is a right output, but all tests are green
return titles.join(", ");
};
getTabIndex = () => {
const selectedDay = this.props.selected;
const preSelectionDay = this.props.preSelection;
const tabIndex =
!(
this.props.showWeekPicker &&
(this.props.showWeekNumber || !this.isStartOfWeek())
) &&
(this.isKeyboardSelected() ||
(this.isSameDay(selectedDay) &&
isSameDay(preSelectionDay, selectedDay)))
? 0
: -1;
return tabIndex;
};
// various cases when we need to apply focus to the preselected day
// focus the day on mount/update so that keyboard navigation works while cycling through months with up or down keys (not for prev and next month buttons)
// prevent focus for these activeElement cases so we don't pull focus from the input as the calendar opens
handleFocusDay = () => {
// only do this while the input isn't focused
// otherwise, typing/backspacing the date manually may steal focus away from the input
this.shouldFocusDay() && this.dayEl.current?.focus({ preventScroll: true });
};
private shouldFocusDay() {
let shouldFocusDay = false;
if (this.getTabIndex() === 0 && this.isSameDay(this.props.preSelection)) {
// there is currently no activeElement and not inline
if (!document.activeElement || document.activeElement === document.body) {
shouldFocusDay = true;
}
// inline version:
// do not focus on initial render to prevent autoFocus issue
// focus after month has changed via keyboard
if (this.props.inline && !this.props.shouldFocusDayInline) {
shouldFocusDay = false;
}
if (this.isDayActiveElement()) {
shouldFocusDay = true;
}
if (this.isDuplicateDay()) {
shouldFocusDay = false;
}
}
return shouldFocusDay;
}
// the activeElement is in the container, and it is another instance of Day
private isDayActiveElement() {
return (
this.props.containerRef?.current?.contains(document.activeElement) &&
document.activeElement?.classList.contains("react-datepicker__day")
);
}
private isDuplicateDay() {
return (
//day is one of the non rendered duplicate days
(this.props.monthShowsDuplicateDaysEnd && this.isAfterMonth()) ||
(this.props.monthShowsDuplicateDaysStart && this.isBeforeMonth())
);
}
renderDayContents = () => {
if (this.props.monthShowsDuplicateDaysEnd && this.isAfterMonth())
return null;
if (this.props.monthShowsDuplicateDaysStart && this.isBeforeMonth())
return null;
return this.props.renderDayContents
? this.props.renderDayContents(getDate(this.props.day), this.props.day)
: getDate(this.props.day);
};
render = () => (
// TODO: Use <option> instead of the "option" role to ensure accessibility across all devices.
<div
ref={this.dayEl}
className={this.getClassNames(this.props.day)}
onKeyDown={this.handleOnKeyDown}
onClick={this.handleClick}
onMouseEnter={
!this.props.usePointerEvent ? this.handleMouseEnter : undefined
}
onPointerEnter={
this.props.usePointerEvent ? this.handleMouseEnter : undefined
}
tabIndex={this.getTabIndex()}
aria-label={this.getAriaLabel()}
role="gridcell"
title={this.getTitle()}
aria-disabled={this.isDisabled()}
aria-current={this.isCurrentDay() ? "date" : undefined}
aria-selected={this.isSelected() || this.isInRange()}
>
{this.renderDayContents()}
{this.getTitle() !== "" && (
<span className="overlay">{this.getTitle()}</span>
)}
</div>
);
}
File diff suppressed because it is too large Load Diff
+124
View File
@@ -0,0 +1,124 @@
import React, { Component, cloneElement } from "react";
interface InputTimeProps {
onChange?: (date: Date) => void;
date?: Date;
timeString?: string;
timeInputLabel?: string;
customTimeInput?: React.ReactElement<{
date?: Date;
value: string;
onChange: (time: string) => void;
}>;
}
interface InputTimeState {
time?: string;
}
/**
* `InputTime` is a React component that manages time input.
*
* @component
* @example
* <InputTime timeString="12:00" />
*
* @param props - The properties that define the `InputTime` component.
* @param props.onChange - Function that is called when the date changes.
* @param props.date - The initial date value.
* @param props.timeString - The initial time string value.
* @param props.timeInputLabel - The label for the time input.
* @param props.customTimeInput - An optional custom time input element.
*
* @returns The `InputTime` component.
*/
export default class InputTime extends Component<
InputTimeProps,
InputTimeState
> {
inputRef: React.RefObject<HTMLInputElement | null> = React.createRef();
constructor(props: InputTimeProps) {
super(props);
this.state = {
time: this.props.timeString,
};
}
static getDerivedStateFromProps(
props: InputTimeProps,
state: InputTimeState,
) {
if (props.timeString !== state.time) {
return {
time: props.timeString,
};
}
// Return null to indicate no change to state.
return null;
}
onTimeChange = (time: InputTimeState["time"]) => {
this.setState({ time });
const { date: propDate } = this.props;
const isPropDateValid = propDate instanceof Date && !isNaN(+propDate);
const date = isPropDateValid ? propDate : new Date();
if (time?.includes(":")) {
const [hours, minutes] = time.split(":") as [string, string];
date.setHours(Number(hours));
date.setMinutes(Number(minutes));
}
this.props.onChange?.(date);
};
renderTimeInput = () => {
const { time } = this.state;
const { date, timeString, customTimeInput } = this.props;
if (customTimeInput) {
return cloneElement(customTimeInput, {
date,
value: time,
onChange: this.onTimeChange,
});
}
return (
<input
type="time"
className="react-datepicker-time__input"
placeholder="Time"
name="time-input"
ref={this.inputRef}
onClick={() => {
this.inputRef.current?.focus();
}}
required
value={time}
onChange={(event) => {
this.onTimeChange(event.target.value || timeString);
}}
/>
);
};
render() {
return (
<div className="react-datepicker__input-time-container">
<div className="react-datepicker-time__caption">
{this.props.timeInputLabel}
</div>
<div className="react-datepicker-time__input-container">
<div className="react-datepicker-time__input">
{this.renderTimeInput()}
</div>
</div>
</div>
);
}
}
File diff suppressed because it is too large Load Diff
+129
View File
@@ -0,0 +1,129 @@
import React, { Component } from "react";
import {
getMonthShortInLocale,
getMonthInLocale,
type Locale,
} from "./date_utils";
import MonthDropdownOptions from "./month_dropdown_options";
interface MonthDropdownOptionsProps extends React.ComponentPropsWithoutRef<
typeof MonthDropdownOptions
> {}
interface MonthDropdownProps extends Omit<
MonthDropdownOptionsProps,
"monthNames" | "onChange" | "onCancel"
> {
dropdownMode: "scroll" | "select";
locale?: Locale;
onChange: (month: number) => void;
useShortMonthInDropdown?: boolean;
}
interface MonthDropdownState {
dropdownVisible: boolean;
}
export default class MonthDropdown extends Component<
MonthDropdownProps,
MonthDropdownState
> {
state: MonthDropdownState = {
dropdownVisible: false,
};
renderSelectOptions = (monthNames: string[]): React.ReactElement[] =>
monthNames.map<React.ReactElement>(
(m: string, i: number): React.ReactElement => (
<option key={m} value={i}>
{m}
</option>
),
);
renderSelectMode = (monthNames: string[]): React.ReactElement => (
<select
value={this.props.month}
className="react-datepicker__month-select"
onChange={(e) => this.onChange(parseInt(e.target.value))}
>
{this.renderSelectOptions(monthNames)}
</select>
);
renderReadView = (
visible: boolean,
monthNames: string[],
): React.ReactElement => (
<button
key="read"
type="button"
style={{ visibility: visible ? "visible" : "hidden" }}
className="react-datepicker__month-read-view"
onClick={this.toggleDropdown}
>
<span className="react-datepicker__month-read-view--down-arrow" />
<span className="react-datepicker__month-read-view--selected-month">
{monthNames[this.props.month]}
</span>
</button>
);
renderDropdown = (monthNames: string[]): React.ReactElement => (
<MonthDropdownOptions
key="dropdown"
{...this.props}
monthNames={monthNames}
onChange={this.onChange}
onCancel={this.toggleDropdown}
/>
);
renderScrollMode = (monthNames: string[]): React.ReactElement[] => {
const { dropdownVisible } = this.state;
const result = [this.renderReadView(!dropdownVisible, monthNames)];
if (dropdownVisible) {
result.unshift(this.renderDropdown(monthNames));
}
return result;
};
onChange = (month: number): void => {
this.toggleDropdown();
if (month !== this.props.month) {
this.props.onChange(month);
}
};
toggleDropdown = (): void =>
this.setState({
dropdownVisible: !this.state.dropdownVisible,
});
render(): React.ReactElement {
const monthNames: string[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(
this.props.useShortMonthInDropdown
? (m: number): string => getMonthShortInLocale(m, this.props.locale)
: (m: number): string => getMonthInLocale(m, this.props.locale),
);
let renderedDropdown: React.ReactElement | React.ReactElement[];
switch (this.props.dropdownMode) {
case "scroll":
renderedDropdown = this.renderScrollMode(monthNames);
break;
case "select":
renderedDropdown = this.renderSelectMode(monthNames);
break;
}
return (
<div
className={`react-datepicker__month-dropdown-container react-datepicker__month-dropdown-container--${this.props.dropdownMode}`}
>
{renderedDropdown}
</div>
);
}
}
+89
View File
@@ -0,0 +1,89 @@
import React, { Component } from "react";
import { ClickOutsideWrapper } from "./click_outside_wrapper";
interface MonthDropdownOptionsProps {
onCancel: VoidFunction;
onChange: (month: number) => void;
month: number;
monthNames: string[];
}
export default class MonthDropdownOptions extends Component<MonthDropdownOptionsProps> {
monthOptionButtonsRef: Record<number, HTMLDivElement | null> = {};
isSelectedMonth = (i: number): boolean => this.props.month === i;
handleOptionKeyDown = (i: number, e: React.KeyboardEvent): void => {
switch (e.key) {
case "Enter":
e.preventDefault();
this.onChange(i);
break;
case "Escape":
e.preventDefault();
this.props.onCancel();
break;
case "ArrowUp":
case "ArrowDown": {
e.preventDefault();
const newMonth =
(i + (e.key === "ArrowUp" ? -1 : 1) + this.props.monthNames.length) %
this.props.monthNames.length;
this.monthOptionButtonsRef[newMonth]?.focus();
break;
}
}
};
renderOptions = (): React.ReactElement[] => {
// Clear refs to prevent memory leaks on re-render
this.monthOptionButtonsRef = {};
return this.props.monthNames.map<React.ReactElement>(
(month: string, i: number): React.ReactElement => (
<div
ref={(el) => {
this.monthOptionButtonsRef[i] = el;
if (this.isSelectedMonth(i)) {
el?.focus();
}
}}
role="button"
tabIndex={0}
className={
this.isSelectedMonth(i)
? "react-datepicker__month-option react-datepicker__month-option--selected_month"
: "react-datepicker__month-option"
}
key={month}
onClick={this.onChange.bind(this, i)}
onKeyDown={this.handleOptionKeyDown.bind(this, i)}
aria-selected={this.isSelectedMonth(i) ? "true" : undefined}
>
{this.isSelectedMonth(i) ? (
<span className="react-datepicker__month-option--selected"></span>
) : (
""
)}
{month}
</div>
),
);
};
onChange = (month: number): void => this.props.onChange(month);
handleClickOutside = (): void => this.props.onCancel();
render(): React.ReactElement {
return (
<ClickOutsideWrapper
className="react-datepicker__month-dropdown"
onClickOutside={this.handleClickOutside}
>
{this.renderOptions()}
</ClickOutsideWrapper>
);
}
}
+165
View File
@@ -0,0 +1,165 @@
import React, { Component } from "react";
import {
addMonths,
addYears,
subYears,
formatDate,
getStartOfMonth,
isAfter,
isSameMonth,
isSameYear,
newDate,
getTime,
type Locale,
} from "./date_utils";
import MonthYearDropdownOptions from "./month_year_dropdown_options";
// Default range: 5 years before and after current date
const DEFAULT_YEAR_RANGE = 5;
interface MonthYearDropdownOptionsProps extends React.ComponentPropsWithoutRef<
typeof MonthYearDropdownOptions
> {}
interface MonthYearDropdownProps extends Omit<
MonthYearDropdownOptionsProps,
"onChange" | "onCancel"
> {
dropdownMode: "scroll" | "select";
onChange: (monthYear: Date) => void;
locale?: Locale;
}
interface MonthYearDropdownState {
dropdownVisible: boolean;
}
export default class MonthYearDropdown extends Component<
MonthYearDropdownProps,
MonthYearDropdownState
> {
state: MonthYearDropdownState = {
dropdownVisible: false,
};
renderSelectOptions = (): React.ReactElement[] => {
// Use defaults if minDate/maxDate not provided
const minDate =
this.props.minDate ?? subYears(this.props.date, DEFAULT_YEAR_RANGE);
const maxDate =
this.props.maxDate ?? addYears(this.props.date, DEFAULT_YEAR_RANGE);
let currDate = getStartOfMonth(minDate);
const lastDate = getStartOfMonth(maxDate);
const options = [];
while (!isAfter(currDate, lastDate)) {
const timePoint = getTime(currDate);
options.push(
<option key={timePoint} value={timePoint}>
{formatDate(currDate, this.props.dateFormat, this.props.locale)}
</option>,
);
currDate = addMonths(currDate, 1);
}
return options;
};
onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
this.onChange(parseInt(event.target.value));
};
renderSelectMode = (): React.ReactElement => (
<select
value={getTime(getStartOfMonth(this.props.date))}
className="react-datepicker__month-year-select"
onChange={this.onSelectChange}
>
{this.renderSelectOptions()}
</select>
);
renderReadView = (visible: boolean): React.ReactElement => {
const yearMonth = formatDate(
this.props.date,
this.props.dateFormat,
this.props.locale,
);
return (
<div
key="read"
style={{ visibility: visible ? "visible" : "hidden" }}
className="react-datepicker__month-year-read-view"
onClick={this.toggleDropdown}
>
<span className="react-datepicker__month-year-read-view--down-arrow" />
<span className="react-datepicker__month-year-read-view--selected-month-year">
{yearMonth}
</span>
</div>
);
};
renderDropdown = (): React.ReactElement => (
<MonthYearDropdownOptions
key="dropdown"
{...this.props}
onChange={this.onChange}
onCancel={this.toggleDropdown}
/>
);
renderScrollMode = (): React.ReactElement[] => {
const { dropdownVisible } = this.state;
const result = [this.renderReadView(!dropdownVisible)];
if (dropdownVisible) {
result.unshift(this.renderDropdown());
}
return result;
};
onChange = (monthYearPoint: number): void => {
this.toggleDropdown();
const changedDate = newDate(monthYearPoint);
if (
isSameYear(this.props.date, changedDate) &&
isSameMonth(this.props.date, changedDate)
) {
return;
}
this.props.onChange(changedDate);
};
toggleDropdown = (): void =>
this.setState({
dropdownVisible: !this.state.dropdownVisible,
});
render(): React.ReactElement {
let renderedDropdown;
switch (this.props.dropdownMode) {
case "scroll":
renderedDropdown = this.renderScrollMode();
break;
case "select":
renderedDropdown = this.renderSelectMode();
break;
}
return (
<div
className={`react-datepicker__month-year-dropdown-container react-datepicker__month-year-dropdown-container--${this.props.dropdownMode}`}
>
{renderedDropdown}
</div>
);
}
}
@@ -0,0 +1,130 @@
import { clsx } from "clsx";
import React, { Component } from "react";
import { ClickOutsideWrapper } from "./click_outside_wrapper";
import {
addMonths,
addYears,
subYears,
formatDate,
getStartOfMonth,
newDate,
isAfter,
isSameMonth,
isSameYear,
getTime,
type Locale,
} from "./date_utils";
// Default range: 5 years before and after current date
const DEFAULT_YEAR_RANGE = 5;
function generateMonthYears(
minDate: Date | undefined,
maxDate: Date | undefined,
currentDate: Date,
): Date[] {
const list = [];
// Use defaults if minDate/maxDate not provided
const effectiveMinDate = minDate ?? subYears(currentDate, DEFAULT_YEAR_RANGE);
const effectiveMaxDate = maxDate ?? addYears(currentDate, DEFAULT_YEAR_RANGE);
let currDate = getStartOfMonth(effectiveMinDate);
const lastDate = getStartOfMonth(effectiveMaxDate);
while (!isAfter(currDate, lastDate)) {
list.push(newDate(currDate));
currDate = addMonths(currDate, 1);
}
return list;
}
interface MonthYearDropdownOptionsProps {
minDate?: Date;
maxDate?: Date;
onCancel: VoidFunction;
onChange: (monthYear: number) => void;
scrollableMonthYearDropdown?: boolean;
date: Date;
dateFormat: string;
locale?: Locale;
}
interface MonthYearDropdownOptionsState {
monthYearsList: Date[];
}
export default class MonthYearDropdownOptions extends Component<
MonthYearDropdownOptionsProps,
MonthYearDropdownOptionsState
> {
constructor(props: MonthYearDropdownOptionsProps) {
super(props);
this.state = {
monthYearsList: generateMonthYears(
this.props.minDate,
this.props.maxDate,
this.props.date,
),
};
}
renderOptions = (): React.ReactElement[] => {
return this.state.monthYearsList.map<React.ReactElement>(
(monthYear: Date): React.ReactElement => {
const monthYearPoint = getTime(monthYear);
const isSameMonthYear =
isSameYear(this.props.date, monthYear) &&
isSameMonth(this.props.date, monthYear);
return (
<div
className={
isSameMonthYear
? "react-datepicker__month-year-option--selected_month-year"
: "react-datepicker__month-year-option"
}
key={monthYearPoint}
onClick={this.onChange.bind(this, monthYearPoint)}
aria-selected={isSameMonthYear ? "true" : undefined}
>
{isSameMonthYear ? (
<span className="react-datepicker__month-year-option--selected">
</span>
) : (
""
)}
{formatDate(monthYear, this.props.dateFormat, this.props.locale)}
</div>
);
},
);
};
onChange = (monthYear: number): void => this.props.onChange(monthYear);
handleClickOutside = (): void => {
this.props.onCancel();
};
render(): React.ReactElement {
const dropdownClass = clsx({
"react-datepicker__month-year-dropdown": true,
"react-datepicker__month-year-dropdown--scrollable":
this.props.scrollableMonthYearDropdown,
});
return (
<ClickOutsideWrapper
className={dropdownClass}
onClickOutside={this.handleClickOutside}
>
{this.renderOptions()}
</ClickOutsideWrapper>
);
}
}
+128
View File
@@ -0,0 +1,128 @@
import { FloatingArrow } from "@floating-ui/react";
import { clsx } from "clsx";
import React, { createElement, useEffect } from "react";
import Portal from "./portal";
import TabLoop from "./tab_loop";
import withFloating from "./with_floating";
import type { FloatingProps } from "./with_floating";
import type { ReactNode } from "react";
interface PortalProps extends Omit<
React.ComponentPropsWithoutRef<typeof Portal>,
"children"
> {}
interface TabLoopProps extends Omit<
React.ComponentPropsWithoutRef<typeof TabLoop>,
"children"
> {}
interface PopperComponentProps
extends Omit<PortalProps, "portalId">, TabLoopProps, FloatingProps {
className?: string;
wrapperClassName?: string;
popperComponent: React.ReactNode;
popperContainer?: React.FC<{ children?: ReactNode | undefined }>;
targetComponent: React.ReactNode;
popperOnKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
showArrow?: boolean;
portalId?: PortalProps["portalId"];
popperTargetRef?: React.RefObject<HTMLElement | null>;
monthHeaderPosition?: "top" | "middle" | "bottom";
}
// Exported for testing purposes
export const PopperComponent: React.FC<PopperComponentProps> = (props) => {
const {
className,
wrapperClassName,
hidePopper = true,
popperComponent,
targetComponent,
enableTabLoop,
popperOnKeyDown,
portalId,
portalHost,
popperProps,
showArrow,
popperTargetRef,
monthHeaderPosition,
} = props;
// When a custom popperTargetRef is provided, use it as the position reference
// This allows the popper to be positioned relative to a specific element
// within the custom input, rather than the wrapper div
useEffect(() => {
if (popperTargetRef?.current) {
popperProps.refs.setPositionReference(popperTargetRef.current);
}
}, [popperTargetRef, popperProps.refs]);
let popper: React.ReactElement | undefined = undefined;
if (!hidePopper) {
const classes = clsx(
"react-datepicker-popper",
!showArrow && "react-datepicker-popper-offset",
monthHeaderPosition === "middle" &&
"react-datepicker-popper--header-middle",
monthHeaderPosition === "bottom" &&
"react-datepicker-popper--header-bottom",
className,
);
popper = (
<TabLoop enableTabLoop={enableTabLoop}>
{/* eslint-disable react-hooks/refs -- Floating UI values are designed to be used during render */}
<div
ref={popperProps.refs.setFloating}
style={popperProps.floatingStyles}
className={classes}
data-placement={popperProps.placement}
onKeyDown={popperOnKeyDown}
>
{popperComponent}
{showArrow && (
<FloatingArrow
ref={popperProps.arrowRef}
context={popperProps.context}
fill="currentColor"
strokeWidth={1}
height={8}
width={16}
style={{ transform: "translateY(-1px)" }}
className="react-datepicker__triangle"
/>
)}
</div>
{/* eslint-enable react-hooks/refs */}
</TabLoop>
);
}
if (props.popperContainer) {
popper = createElement(props.popperContainer, {}, popper);
}
if (portalId && !hidePopper) {
popper = (
<Portal portalId={portalId} portalHost={portalHost}>
{popper}
</Portal>
);
}
const wrapperClasses = clsx("react-datepicker-wrapper", wrapperClassName);
return (
<>
{/* eslint-disable-next-line react-hooks/refs -- Floating UI refs are designed to be used during render */}
<div ref={popperProps.refs.setReference} className={wrapperClasses}>
{targetComponent}
</div>
{popper}
</>
);
};
export default withFloating<PopperComponentProps>(PopperComponent);
+54
View File
@@ -0,0 +1,54 @@
import { Component } from "react";
import ReactDOM from "react-dom";
import type React from "react";
interface PortalProps {
children: React.ReactNode;
portalId: string;
portalHost?: ShadowRoot;
}
/**
* `Portal` is a React component that allows you to render children into a DOM node
* that exists outside the DOM hierarchy of the parent component.
*
* @class
* @param {PortalProps} props - The properties that define the `Portal` component.
* @property {React.ReactNode} props.children - The children to be rendered into the `Portal`.
* @property {string} props.portalId - The id of the DOM node into which the `Portal` will render.
* @property {ShadowRoot} [props.portalHost] - The DOM node to host the `Portal`.
*/
class Portal extends Component<PortalProps> {
constructor(props: PortalProps) {
super(props);
this.el = document.createElement("div");
}
componentDidMount() {
this.portalRoot = (this.props.portalHost || document).getElementById(
this.props.portalId,
);
if (!this.portalRoot) {
this.portalRoot = document.createElement("div");
this.portalRoot.setAttribute("id", this.props.portalId);
(this.props.portalHost || document.body).appendChild(this.portalRoot);
}
this.portalRoot.appendChild(this.el);
}
componentWillUnmount() {
if (this.portalRoot) {
this.portalRoot.removeChild(this.el);
}
}
private el: HTMLDivElement;
private portalRoot: HTMLElement | null = null;
render(): React.ReactPortal {
return ReactDOM.createPortal(this.props.children, this.el);
}
}
export default Portal;
@@ -0,0 +1,5 @@
@use "sass:meta";
:global {
@include meta.load-css("datepicker");
}
+816
View File
@@ -0,0 +1,816 @@
@use "sass:color";
@use "variables" as *;
@use "mixins" as *;
.react-datepicker__sr-only {
// sr-only utility class for accessibility
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.react-datepicker-wrapper {
display: inline-block;
padding: 0;
border: 0;
}
.react-datepicker {
font-family: $datepicker__font-family;
font-size: $datepicker__font-size;
background-color: #fff;
color: $datepicker__text-color;
border: $datepicker__border;
border-radius: $datepicker__border-radius;
display: inline-block;
position: relative;
// Reverting value set in .react-datepicker-popper
line-height: initial;
}
.react-datepicker--time-only {
.react-datepicker__time-container {
border-left: 0;
}
.react-datepicker__time,
.react-datepicker__time-box {
border-bottom-left-radius: 0.375em;
border-bottom-right-radius: 0.375em;
}
}
.react-datepicker-popper {
z-index: 1;
// Eliminating extra space at the bottom of the container
line-height: 0;
.react-datepicker__triangle {
stroke: $datepicker__border-color;
}
&[data-placement^="bottom"] {
.react-datepicker__triangle {
fill: $datepicker__background-color;
color: $datepicker__background-color;
}
}
&[data-placement^="top"] {
.react-datepicker__triangle {
fill: #fff;
color: #fff;
}
}
&--header-middle,
&--header-bottom {
&[data-placement^="bottom"] {
.react-datepicker__triangle {
fill: #fff;
color: #fff;
}
}
}
&--header-bottom {
&[data-placement^="top"] {
.react-datepicker__triangle {
fill: $datepicker__background-color;
color: $datepicker__background-color;
}
}
}
}
.react-datepicker__header {
text-align: center;
background-color: $datepicker__background-color;
border-bottom: $datepicker__border;
border-top-left-radius: $datepicker__border-radius;
padding: 8px 0;
position: relative;
&--time {
padding-bottom: 8px;
padding-left: 5px;
padding-right: 5px;
&:not(&--only) {
border-top-left-radius: 0;
}
}
&:not(&--has-time-select, &--middle, &--bottom) {
border-top-right-radius: $datepicker__border-radius;
}
// Header in middle position (between day names and days)
&--middle {
border-top: $datepicker__border;
border-radius: 0;
margin-top: 4px;
}
// Header in bottom position (at calendar bottom)
&--bottom {
border-bottom: none;
border-top: $datepicker__border;
border-radius: 0 0 $datepicker__border-radius $datepicker__border-radius;
}
}
// Wrapper for header in middle/bottom positions
.react-datepicker__header-wrapper {
position: relative;
.react-datepicker__navigation--next--with-time:not(
.react-datepicker__navigation--next--with-today-button
) {
right: 2px;
}
}
.react-datepicker__year-dropdown-container--select,
.react-datepicker__month-dropdown-container--select,
.react-datepicker__month-year-dropdown-container--select,
.react-datepicker__year-dropdown-container--scroll,
.react-datepicker__month-dropdown-container--scroll,
.react-datepicker__month-year-dropdown-container--scroll {
display: inline-block;
margin: 0 15px;
}
.react-datepicker__month-select,
.react-datepicker__year-select,
.react-datepicker__month-year-select {
background-color: transparent;
border: 1px solid $datepicker__border-color;
border-radius: $datepicker__border-radius;
color: inherit;
cursor: pointer;
font-family: inherit;
font-size: inherit;
margin-top: 5px;
padding: 2px 5px;
&:focus-visible {
outline: auto 1px;
}
}
.react-datepicker__current-month,
.react-datepicker-time__header,
.react-datepicker-year-header {
margin-top: 0;
color: $datepicker__header-color;
font-weight: bold;
font-size: $datepicker__font-size * 1.18;
}
h2.react-datepicker__current-month {
padding: 0;
margin: 0;
}
.react-datepicker-time__header {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.react-datepicker__navigation {
align-items: center;
background: none;
display: flex;
justify-content: center;
text-align: center;
cursor: pointer;
position: absolute;
top: 2px;
padding: 0;
border: none;
z-index: 1;
height: $datepicker__navigation-button-size;
width: $datepicker__navigation-button-size;
text-indent: -999em;
overflow: hidden;
&--previous {
left: 2px;
}
&--next {
right: 2px;
&--with-time:not(&--with-today-button) {
right: 85px;
}
}
&--years {
position: relative;
top: 0;
display: block;
margin-left: auto;
margin-right: auto;
&-previous {
top: 4px;
}
&-upcoming {
top: -4px;
}
}
&:hover {
*::before {
border-color: color.adjust($datepicker__muted-color, $lightness: -15%);
}
}
}
.react-datepicker__navigation-icon {
position: relative;
top: -1px;
font-size: 20px;
width: 0;
&::before {
@extend %navigation-chevron;
}
&--next {
left: -2px;
&::before {
transform: rotate(45deg);
left: -7px;
}
}
&--previous {
right: -2px;
&::before {
transform: rotate(225deg);
right: -7px;
}
}
}
.react-datepicker__month-container {
float: left;
}
.react-datepicker__year {
margin: $datepicker__margin;
text-align: center;
&-wrapper {
display: flex;
flex-wrap: wrap;
max-width: 180px;
}
.react-datepicker__year-text {
display: inline-block;
width: 5em;
margin: 2px;
}
}
.react-datepicker__month {
margin: $datepicker__margin;
text-align: center;
.react-datepicker__month-text,
.react-datepicker__quarter-text {
display: inline-block;
width: 5em;
margin: 2px;
}
}
.react-datepicker__input-time-container {
clear: both;
width: 100%;
float: left;
margin: 5px 0 10px 15px;
text-align: left;
.react-datepicker-time__caption {
display: inline-block;
}
.react-datepicker-time__input-container {
display: inline-block;
.react-datepicker-time__input {
display: inline-block;
margin-left: 10px;
input {
width: auto;
}
input[type="time"]::-webkit-inner-spin-button,
input[type="time"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="time"] {
-moz-appearance: textfield;
}
}
.react-datepicker-time__delimiter {
margin-left: 5px;
display: inline-block;
}
}
}
.react-datepicker__time-container {
float: right;
border-left: $datepicker__border;
width: 85px;
&--with-today-button {
display: inline;
border: 1px solid #aeaeae;
border-radius: 0.375em;
position: absolute;
right: -87px;
top: 0;
}
.react-datepicker__time {
position: relative;
background: white;
border-bottom-right-radius: 0.375em;
.react-datepicker__time-box {
width: 85px;
overflow-x: hidden;
margin: 0 auto;
text-align: center;
border-bottom-right-radius: 0.375em;
ul.react-datepicker__time-list {
list-style: none;
margin: 0;
height: calc(195px + (#{$datepicker__item-size} / 2));
overflow-y: scroll;
padding-right: 0;
padding-left: 0;
width: 100%;
box-sizing: content-box;
li.react-datepicker__time-list-item {
height: 30px;
padding: 5px 10px;
white-space: nowrap;
&:hover {
cursor: pointer;
background-color: $datepicker__background-color;
}
&--selected {
background-color: $datepicker__selected-color;
color: white;
font-weight: bold;
&:hover {
background-color: $datepicker__selected-color;
}
}
&--disabled {
color: $datepicker__muted-color;
&:hover {
cursor: default;
background-color: transparent;
}
}
}
}
}
}
}
.react-datepicker__week-number {
color: $datepicker__muted-color;
display: inline-block;
width: $datepicker__item-size;
line-height: $datepicker__item-size;
text-align: center;
margin: $datepicker__day-margin;
&.react-datepicker__week-number--clickable {
cursor: pointer;
&:not(.react-datepicker__week-number--selected):hover {
border-radius: $datepicker__border-radius;
background-color: $datepicker__background-color;
}
}
&--selected {
border-radius: $datepicker__border-radius;
background-color: $datepicker__selected-color;
color: #fff;
&:hover {
background-color: color.adjust(
$datepicker__selected-color,
$lightness: -5%
);
}
}
}
.react-datepicker__day-names {
text-align: center;
white-space: nowrap;
margin-bottom: -8px;
}
.react-datepicker__week {
white-space: nowrap;
}
.react-datepicker__day-name,
.react-datepicker__day,
.react-datepicker__time-name {
color: $datepicker__text-color;
display: inline-block;
width: $datepicker__item-size;
line-height: $datepicker__item-size;
text-align: center;
margin: $datepicker__day-margin;
&--disabled {
cursor: default;
color: $datepicker__muted-color;
}
}
.react-datepicker__day,
.react-datepicker__month-text,
.react-datepicker__quarter-text,
.react-datepicker__year-text {
cursor: pointer;
&:not([aria-disabled="true"]):hover {
border-radius: $datepicker__border-radius;
background-color: $datepicker__background-color;
}
&--today {
font-weight: bold;
}
&--highlighted {
border-radius: $datepicker__border-radius;
background-color: $datepicker__highlighted-color;
color: #fff;
&:not([aria-disabled="true"]):hover {
background-color: color.adjust(
$datepicker__highlighted-color,
$lightness: -5%
);
}
&-custom-1 {
color: magenta;
}
&-custom-2 {
color: green;
}
}
&--holidays {
position: relative;
border-radius: $datepicker__border-radius;
background-color: $datepicker__holidays-color;
color: #fff;
.overlay {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition:
visibility 0s,
opacity 0.3s ease-in-out;
}
&:not([aria-disabled="true"]):hover {
background-color: color.adjust(
$datepicker__holidays-color,
$lightness: -10%
);
}
&:hover .overlay {
visibility: visible;
opacity: 1;
}
}
&--selected,
&--in-selecting-range,
&--in-range {
border-radius: $datepicker__border-radius;
background-color: $datepicker__selected-color;
color: #fff;
&:not([aria-disabled="true"]):hover {
background-color: color.adjust(
$datepicker__selected-color,
$lightness: -5%
);
}
}
&--keyboard-selected {
border-radius: $datepicker__border-radius;
background-color: color.adjust(
$datepicker__selected-color,
$lightness: 45%
);
color: rgb(0, 0, 0);
&:not([aria-disabled="true"]):hover {
background-color: color.adjust(
$datepicker__selected-color,
$lightness: -5%
);
color: #fff;
}
}
&--in-selecting-range:not(&--in-range) {
background-color: $datepicker__selected-color--disabled;
}
&--in-range:not(&--in-selecting-range) {
.react-datepicker__month--selecting-range &,
.react-datepicker__year--selecting-range & {
background-color: $datepicker__background-color;
color: $datepicker__text-color;
}
}
&--disabled {
cursor: default;
color: $datepicker__muted-color;
.overlay {
position: absolute;
bottom: 70%;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 4px;
border-radius: 4px;
white-space: nowrap;
visibility: hidden;
opacity: 0;
transition:
visibility 0s,
opacity 0.3s ease-in-out;
}
}
}
.react-datepicker__input-container {
position: relative;
display: inline-block;
width: 100%;
.react-datepicker__calendar-icon {
position: absolute;
padding: 0.625em;
box-sizing: content-box;
}
}
.react-datepicker__view-calendar-icon {
input {
padding: 6px 10px 5px 25px;
}
}
.react-datepicker__year-read-view,
.react-datepicker__month-read-view,
.react-datepicker__month-year-read-view {
border: 1px solid transparent;
border-radius: $datepicker__border-radius;
position: relative;
&:hover {
cursor: pointer;
.react-datepicker__year-read-view--down-arrow,
.react-datepicker__month-read-view--down-arrow {
border-top-color: color.adjust(
$datepicker__muted-color,
$lightness: -10%
);
}
}
&--down-arrow {
@extend %navigation-chevron;
transform: rotate(135deg);
right: -16px;
top: 0;
}
}
.react-datepicker__year-dropdown,
.react-datepicker__month-dropdown,
.react-datepicker__month-year-dropdown {
background-color: $datepicker__background-color;
position: absolute;
width: 50%;
left: 25%;
top: 30px;
z-index: 1;
text-align: center;
border-radius: $datepicker__border-radius;
border: $datepicker__border;
&:hover {
cursor: pointer;
}
&--scrollable {
height: 150px;
overflow-y: scroll;
}
}
.react-datepicker__year-option,
.react-datepicker__month-option,
.react-datepicker__month-year-option {
line-height: 20px;
width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
&:first-of-type {
border-top-left-radius: $datepicker__border-radius;
border-top-right-radius: $datepicker__border-radius;
}
&:last-of-type {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-bottom-left-radius: $datepicker__border-radius;
border-bottom-right-radius: $datepicker__border-radius;
}
&:hover {
background-color: $datepicker__muted-color;
.react-datepicker__navigation--years-upcoming {
border-bottom-color: color.adjust(
$datepicker__muted-color,
$lightness: -10%
);
}
.react-datepicker__navigation--years-previous {
border-top-color: color.adjust(
$datepicker__muted-color,
$lightness: -10%
);
}
}
&--selected {
position: absolute;
left: 15px;
}
}
.react-datepicker__close-icon {
cursor: pointer;
background-color: transparent;
border: 0;
outline: 0;
padding: 0 6px 0 0;
position: absolute;
top: 0;
right: 0;
height: 100%;
display: table-cell;
vertical-align: middle;
&::after {
cursor: pointer;
background-color: $datepicker__selected-color;
color: #fff;
border-radius: 50%;
height: 16px;
width: 16px;
padding: 2px;
font-size: 12px;
line-height: 1;
text-align: center;
display: table-cell;
vertical-align: middle;
content: "\00d7";
}
&--disabled {
cursor: default;
&::after {
cursor: default;
background-color: $datepicker__muted-color;
}
}
}
.react-datepicker__today-button {
background: $datepicker__background-color;
border-top: $datepicker__border;
cursor: pointer;
text-align: center;
font-weight: bold;
padding: 5px 0;
clear: left;
}
.react-datepicker__portal {
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgb(0, 0, 0, 0.8);
left: 0;
top: 0;
justify-content: center;
align-items: center;
display: flex;
z-index: 2147483647;
}
.react-datepicker__children-container {
width: 17.25em;
margin: 0.5em;
padding-right: 0.25em;
padding-left: 0.25em;
height: auto;
}
.react-datepicker__aria-live {
position: absolute;
clip-path: circle(0);
border: 0;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
width: 1px;
white-space: nowrap;
}
.react-datepicker__calendar-icon {
width: 1em;
height: 1em;
vertical-align: -0.125em;
}
.react-datepicker-popper-offset {
margin-top: -0.7em;
}
+19
View File
@@ -0,0 +1,19 @@
@use "variables";
%navigation-chevron {
border-color: variables.$datepicker__muted-color;
border-style: solid;
border-width: 3px 3px 0 0;
content: "";
display: block;
height: 9px;
position: absolute;
top: 6px;
width: 9px;
&--disabled,
&--disabled:hover {
border-color: variables.$datepicker__navigation-disabled-color;
cursor: default;
}
}
+25
View File
@@ -0,0 +1,25 @@
@use "sass:color";
$datepicker__background-color: #f0f0f0 !default;
$datepicker__border-color: #aeaeae !default;
$datepicker__highlighted-color: #3dcc4a !default;
$datepicker__holidays-color: #ff6803 !default;
$datepicker__muted-color: #ccc !default;
$datepicker__selected-color: #216ba5 !default;
$datepicker__selected-color--disabled: rgba($datepicker__selected-color, 0.5);
$datepicker__text-color: #000 !default;
$datepicker__header-color: #000 !default;
$datepicker__navigation-disabled-color: color.adjust(
$datepicker__muted-color,
$lightness: 10%
) !default;
$datepicker__border: 1px solid $datepicker__border-color;
$datepicker__border-radius: 0.3rem !default;
$datepicker__day-margin: 0.208em !default;
$datepicker__font-size: 0.8rem !default;
// stylelint-disable-next-line scss/dollar-variable-colon-space-after
$datepicker__font-family:
"Helvetica Neue", helvetica, arial, sans-serif !default;
$datepicker__item-size: 2.125em !default;
$datepicker__margin: 0.5em !default;
$datepicker__navigation-button-size: 32px !default;
+108
View File
@@ -0,0 +1,108 @@
import React, { Component, createRef } from "react";
import type { ReactNode } from "react";
interface TabLoopProps {
enableTabLoop?: boolean;
children?: ReactNode | undefined;
}
const focusableElementsSelector =
"[tabindex], a, button, input, select, textarea";
const focusableFilter = (
node:
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement
| HTMLAnchorElement,
) => {
if (node instanceof HTMLAnchorElement) {
return node.tabIndex !== -1;
}
return !node.disabled && node.tabIndex !== -1;
};
/**
* `TabLoop` is a React component that manages tabbing behavior for its children.
*
* TabLoop prevents the user from tabbing outside of the popper
* It creates a tabindex loop so that "Tab" on the last element will focus the first element
* and "Shift Tab" on the first element will focus the last element
*
* @component
* @example
* <TabLoop enableTabLoop={true}>
* <ChildComponent />
* </TabLoop>
*
* @param props - The properties that define the `TabLoop` component.
* @param props.children - The child components.
* @param props.enableTabLoop - Whether to enable the tab loop.
*
* @returns The `TabLoop` component.
*/
export default class TabLoop extends Component<TabLoopProps> {
static defaultProps = {
enableTabLoop: true,
};
constructor(props: TabLoopProps) {
super(props);
this.tabLoopRef = createRef();
}
private tabLoopRef: React.RefObject<HTMLDivElement | null>;
/**
* `getTabChildren` is a method of the `TabLoop` class that retrieves all tabbable children of the component.
*
* This method uses the `tabbable` library to find all tabbable elements within the `TabLoop` component.
* It then filters out any elements that are not visible.
*
* @returns An array of all tabbable and visible children of the `TabLoop` component.
*/
getTabChildren = () =>
Array.prototype.slice
.call(
this.tabLoopRef.current?.querySelectorAll(focusableElementsSelector),
1,
-1,
)
.filter(focusableFilter);
handleFocusStart = () => {
const tabChildren = this.getTabChildren();
tabChildren &&
tabChildren.length > 1 &&
tabChildren[tabChildren.length - 1].focus();
};
handleFocusEnd = () => {
const tabChildren = this.getTabChildren();
tabChildren && tabChildren.length > 1 && tabChildren[0].focus();
};
render(): React.ReactNode {
if (!(this.props.enableTabLoop ?? TabLoop.defaultProps.enableTabLoop)) {
return this.props.children;
}
return (
<div className="react-datepicker__tab-loop" ref={this.tabLoopRef}>
<div
className="react-datepicker__tab-loop__start"
tabIndex={0}
onFocus={this.handleFocusStart}
/>
{this.props.children}
<div
className="react-datepicker__tab-loop__end"
tabIndex={0}
onFocus={this.handleFocusEnd}
/>
</div>
);
}
}
+340
View File
@@ -0,0 +1,340 @@
import { render } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";
import React from "react";
import DatePicker from "../index";
import { newDate, addDays } from "../date_utils";
expect.extend(toHaveNoViolations);
describe("Accessibility Tests", () => {
describe("Basic DatePicker", () => {
it("should work with proper labeling", async () => {
const { container } = render(
<div>
<label htmlFor="datepicker">Select date</label>
<DatePicker id="datepicker" selected={newDate()} />
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with placeholder", async () => {
const { container } = render(
<DatePicker placeholderText="Select date" selected={newDate()} />,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with aria-label", async () => {
const { container } = render(
<DatePicker
placeholderText="Select date"
aria-label="Choose a date"
selected={newDate()}
/>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work when disabled", async () => {
const { container } = render(
<div>
<label htmlFor="disabled-picker">Select date</label>
<DatePicker id="disabled-picker" selected={newDate()} disabled />
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work when readonly", async () => {
const { container } = render(
<div>
<label htmlFor="readonly-picker">Select date</label>
<DatePicker id="readonly-picker" selected={newDate()} readOnly />
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Opened DatePicker", () => {
it("should not have violations when calendar is open", async () => {
// FAILING: ARIA structure issues - role="row" needs proper parent container
const { container } = render(
<div>
<label htmlFor="open-picker">Select date</label>
<DatePicker id="open-picker" selected={newDate()} open />
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with inline calendar", async () => {
// FAILING: ARIA structure issues - role="row" needs proper parent container
const { container } = render(
<div>
<h2 id="calendar-title">Select a date</h2>
<DatePicker
selected={newDate()}
inline
aria-labelledby="calendar-title"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Date Range Picker", () => {
it("should work with date range picker", async () => {
const { container } = render(
<div>
<label htmlFor="range-picker">Select date range</label>
<DatePicker
id="range-picker"
selectsRange
startDate={newDate()}
endDate={addDays(newDate(), 7)}
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with open range picker", async () => {
// FAILING: ARIA structure issues - role="row" needs proper parent container
const { container } = render(
<div>
<label htmlFor="open-range-picker">Select date range</label>
<DatePicker
id="open-range-picker"
selectsRange
startDate={newDate()}
endDate={addDays(newDate(), 7)}
open
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Time Selection", () => {
it("should work with time selection", async () => {
const { container } = render(
<div>
<label htmlFor="time-picker">Select date and time</label>
<DatePicker
id="time-picker"
selected={newDate()}
showTimeSelect
dateFormat="MMMM d, yyyy h:mm aa"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with time input", async () => {
const { container } = render(
<div>
<label htmlFor="time-input-picker">Select date and time</label>
<DatePicker
id="time-input-picker"
selected={newDate()}
showTimeInput
dateFormat="MMMM d, yyyy h:mm aa"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with time only", async () => {
const { container } = render(
<div>
<label htmlFor="time-only-picker">Select time</label>
<DatePicker
id="time-only-picker"
selected={newDate()}
showTimeSelect
showTimeSelectOnly
timeIntervals={15}
timeCaption="Time"
dateFormat="h:mm aa"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Month and Year Pickers", () => {
it("should work with month picker", async () => {
const { container } = render(
<div>
<label htmlFor="month-picker">Select month</label>
<DatePicker
id="month-picker"
selected={newDate()}
dateFormat="MM/yyyy"
showMonthYearPicker
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with year picker", async () => {
const { container } = render(
<div>
<label htmlFor="year-picker">Select year</label>
<DatePicker
id="year-picker"
selected={newDate()}
showYearPicker
dateFormat="yyyy"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with quarter picker", async () => {
const { container } = render(
<div>
<label htmlFor="quarter-picker">Select quarter</label>
<DatePicker
id="quarter-picker"
selected={newDate()}
showQuarterYearPicker
dateFormat="'Q'Q yyyy"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Week Selection", () => {
it("should work with week picker", async () => {
const { container } = render(
<div>
<label htmlFor="week-picker">Select week</label>
<DatePicker
id="week-picker"
selected={newDate()}
showWeekPicker
dateFormat="'Week' w, yyyy"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with week numbers shown", async () => {
// FAILING: ARIA children requirements - role="listbox" has incorrect child elements
const { container } = render(
<div>
<label htmlFor="week-numbers-picker">Select date</label>
<DatePicker
id="week-numbers-picker"
selected={newDate()}
showWeekNumbers
open
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Multiple Months", () => {
it("should work with multiple months", async () => {
// FAILING: ARIA structure issues - role="row" needs proper parent container
const { container } = render(
<div>
<label htmlFor="multi-month-picker">Select date</label>
<DatePicker
id="multi-month-picker"
selected={newDate()}
monthsShown={2}
open
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Custom Components", () => {
it("should work with clear button", async () => {
const { container } = render(
<div>
<label htmlFor="clearable-picker">Select date</label>
<DatePicker
id="clearable-picker"
selected={newDate()}
isClearable
clearButtonTitle="Clear date"
/>
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with calendar icon", async () => {
const { container } = render(
<div>
<label htmlFor="icon-picker">Select date</label>
<DatePicker id="icon-picker" selected={newDate()} showIcon />
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe("Edge Cases", () => {
it("should work with no selected date", async () => {
const { container } = render(
<div>
<label htmlFor="empty-picker">Select date</label>
<DatePicker id="empty-picker" placeholderText="Choose a date" />
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("should work with portal", async () => {
const { container } = render(
<div>
<label htmlFor="portal-picker">Select date</label>
<DatePicker id="portal-picker" selected={newDate()} withPortal open />
</div>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
});
@@ -0,0 +1,113 @@
import { render } from "@testing-library/react";
import React from "react";
import CalendarContainer from "../calendar_container";
import { CalendarContainer as CalendarContainerFromIndex } from "../index";
describe("CalendarContainer", () => {
it("renders with default props", () => {
const { container } = render(
<CalendarContainer>
<div>Test Content</div>
</CalendarContainer>,
);
const dialog = container.querySelector('[role="dialog"]');
expect(dialog).toBeTruthy();
expect(dialog?.getAttribute("aria-label")).toBe("Choose Date");
expect(dialog?.getAttribute("aria-modal")).toBe("true");
expect(dialog?.textContent).toBe("Test Content");
});
it("exposes CalendarContainer via the package entry point", () => {
const { container } = render(
<CalendarContainerFromIndex>
<div>Entry Content</div>
</CalendarContainerFromIndex>,
);
expect(container.querySelector('[role="dialog"]')).toBeTruthy();
});
it("renders with showTimeSelectOnly prop", () => {
const { container } = render(
<CalendarContainer showTimeSelectOnly>
<div>Time Content</div>
</CalendarContainer>,
);
const dialog = container.querySelector('[role="dialog"]');
expect(dialog?.getAttribute("aria-label")).toBe("Choose Time");
});
it("renders with showTime prop", () => {
const { container } = render(
<CalendarContainer showTime>
<div>Date and Time Content</div>
</CalendarContainer>,
);
const dialog = container.querySelector('[role="dialog"]');
expect(dialog?.getAttribute("aria-label")).toBe("Choose Date and Time");
});
it("renders with both showTime and showTimeSelectOnly props", () => {
const { container } = render(
<CalendarContainer showTime showTimeSelectOnly>
<div>Content</div>
</CalendarContainer>,
);
const dialog = container.querySelector('[role="dialog"]');
// showTimeSelectOnly takes precedence
expect(dialog?.getAttribute("aria-label")).toBe("Choose Time");
});
it("applies custom className", () => {
const { container } = render(
<CalendarContainer className="custom-class">
<div>Content</div>
</CalendarContainer>,
);
const dialog = container.querySelector('[role="dialog"]');
expect(dialog?.className).toBe("custom-class");
});
it("renders children correctly", () => {
const { container } = render(
<CalendarContainer>
<div data-testid="child-1">Child 1</div>
<div data-testid="child-2">Child 2</div>
</CalendarContainer>,
);
expect(container.querySelector('[data-testid="child-1"]')).toBeTruthy();
expect(container.querySelector('[data-testid="child-2"]')).toBeTruthy();
});
it("renders with proper ARIA attributes", () => {
const { container } = render(
<CalendarContainer>
<div>Content</div>
</CalendarContainer>,
);
const dialog = container.querySelector('[role="dialog"]');
expect(dialog?.getAttribute("role")).toBe("dialog");
expect(dialog?.getAttribute("aria-modal")).toBe("true");
expect(dialog?.getAttribute("aria-label")).toBe("Choose Date");
});
it("has translate='no' to prevent browser auto-translation breaking the calendar", () => {
// Fixes #5824 - Safari auto-translate breaks calendar navigation
const { container } = render(
<CalendarContainer>
<div>Content</div>
</CalendarContainer>,
);
const dialog = container.querySelector('[role="dialog"]');
expect(dialog?.getAttribute("translate")).toBe("no");
});
});
+192
View File
@@ -0,0 +1,192 @@
import { render, fireEvent } from "@testing-library/react";
import React from "react";
import CalendarIcon from "../calendar_icon";
import { IconParkSolidApplication } from "./helper_components/calendar_icon";
import { safeQuerySelector } from "./test_utils";
describe("CalendarIcon", () => {
let onClickMock: jest.Mock;
beforeEach(() => {
onClickMock = jest.fn();
});
afterEach(() => {
onClickMock.mockClear();
});
it("renders a custom SVG icon when provided", () => {
const { container } = render(
<CalendarIcon icon={<IconParkSolidApplication />} />,
);
expect(
container.querySelectorAll('[data-testid="icon-park-solid-application"]'),
).toHaveLength(1);
});
it("renders a FontAwesome icon when provided", () => {
const { container } = render(<CalendarIcon icon="fa-example-icon" />);
expect(container.querySelectorAll("i.fa-example-icon")).toHaveLength(1);
});
it("renders a default SVG icon when no icon is provided", () => {
const { container } = render(<CalendarIcon />);
expect(
container.querySelectorAll("svg.react-datepicker__calendar-icon"),
).toHaveLength(1);
});
it("should fire onClick event when the icon is clicked", () => {
const { container } = render(<CalendarIcon onClick={onClickMock} />);
const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);
fireEvent.click(icon);
expect(onClickMock).toHaveBeenCalledTimes(1);
});
it("should fire onClick event on the click of font-awesome icon when provided", () => {
const { container } = render(
<CalendarIcon icon="fa-example-icon" onClick={onClickMock} />,
);
const icon = safeQuerySelector(container, "i.fa-example-icon");
fireEvent.click(icon);
expect(onClickMock).toHaveBeenCalledTimes(1);
});
it("should fire onClick event on the click of custom icon component when provided", () => {
const onClickCustomIcon = jest.fn();
const { container } = render(
<CalendarIcon
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
onClick={onClickCustomIcon}
/>
}
onClick={onClickMock}
/>,
);
const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);
fireEvent.click(icon);
expect(onClickMock).toHaveBeenCalledTimes(1);
expect(onClickCustomIcon).toHaveBeenCalledTimes(1);
});
it("should fire only custom icon onClick when CalendarIcon onClick is not provided", () => {
const onClickCustomIcon = jest.fn();
const { container } = render(
<CalendarIcon
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
onClick={onClickCustomIcon}
/>
}
/>,
);
const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);
fireEvent.click(icon);
// Lines 55-57: custom icon onClick is called
expect(onClickCustomIcon).toHaveBeenCalledTimes(1);
});
it("should fire only CalendarIcon onClick when custom icon onClick is not provided", () => {
const { container } = render(
<CalendarIcon
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
/>
}
onClick={onClickMock}
/>,
);
const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);
fireEvent.click(icon);
// Lines 59-61: CalendarIcon onClick is called
expect(onClickMock).toHaveBeenCalledTimes(1);
});
it("should handle custom icon without onClick prop", () => {
const { container } = render(
<CalendarIcon
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
/>
}
/>,
);
const icon = safeQuerySelector(
container,
"svg.react-datepicker__calendar-icon",
);
// Should not throw when clicking without any onClick handlers
expect(() => fireEvent.click(icon)).not.toThrow();
});
it("should apply className to custom icon", () => {
const { container } = render(
<CalendarIcon
icon={<IconParkSolidApplication />}
className="custom-class"
/>,
);
const icon = container.querySelector(".custom-class");
expect(icon).not.toBeNull();
});
it("should apply className to string icon", () => {
const { container } = render(
<CalendarIcon icon="fa-calendar" className="custom-class" />,
);
const icon = container.querySelector("i.custom-class");
expect(icon).not.toBeNull();
});
it("should apply className to default SVG icon", () => {
const { container } = render(<CalendarIcon className="custom-class" />);
const icon = container.querySelector("svg.custom-class");
expect(icon).not.toBeNull();
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,282 @@
import { render, fireEvent } from "@testing-library/react";
import React from "react";
import { ClickOutsideWrapper } from "../click_outside_wrapper";
describe("ClickOutsideWrapper", () => {
let onClickOutsideMock: jest.Mock;
beforeEach(() => {
onClickOutsideMock = jest.fn();
});
afterEach(() => {
onClickOutsideMock.mockClear();
});
it("renders children correctly", () => {
const { container } = render(
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div data-testid="child">Test Content</div>
</ClickOutsideWrapper>,
);
expect(container.querySelector('[data-testid="child"]')).toBeTruthy();
});
it("calls onClickOutside when clicking outside the wrapper", () => {
const { container } = render(
<div>
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div data-testid="inside">Inside</div>
</ClickOutsideWrapper>
<div data-testid="outside">Outside</div>
</div>,
);
const outsideElement = container.querySelector(
'[data-testid="outside"]',
) as HTMLElement;
fireEvent.mouseDown(outsideElement);
expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
});
it("does not call onClickOutside when clicking inside the wrapper", () => {
const { container } = render(
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div data-testid="inside">Inside</div>
</ClickOutsideWrapper>,
);
const insideElement = container.querySelector(
'[data-testid="inside"]',
) as HTMLElement;
fireEvent.mouseDown(insideElement);
expect(onClickOutsideMock).not.toHaveBeenCalled();
});
it("applies custom className", () => {
const { container } = render(
<ClickOutsideWrapper
onClickOutside={onClickOutsideMock}
className="custom-class"
>
<div>Content</div>
</ClickOutsideWrapper>,
);
const wrapper = container.firstChild as HTMLElement;
expect(wrapper.className).toBe("custom-class");
});
it("applies custom style", () => {
const customStyle = { backgroundColor: "red", padding: "10px" };
const { container } = render(
<ClickOutsideWrapper
onClickOutside={onClickOutsideMock}
style={customStyle}
>
<div>Content</div>
</ClickOutsideWrapper>,
);
const wrapper = container.firstChild as HTMLElement;
expect(wrapper.style.backgroundColor).toBe("red");
expect(wrapper.style.padding).toBe("10px");
});
it("does not call onClickOutside when clicking on element with ignoreClass", () => {
const { container } = render(
<div>
<ClickOutsideWrapper
onClickOutside={onClickOutsideMock}
ignoreClass="ignore-me"
>
<div data-testid="inside">Inside</div>
</ClickOutsideWrapper>
<div className="ignore-me" data-testid="ignored">
Ignored
</div>
</div>,
);
const ignoredElement = container.querySelector(
'[data-testid="ignored"]',
) as HTMLElement;
fireEvent.mouseDown(ignoredElement);
expect(onClickOutsideMock).not.toHaveBeenCalled();
});
it("calls onClickOutside when clicking on element without ignoreClass", () => {
const { container } = render(
<div>
<ClickOutsideWrapper
onClickOutside={onClickOutsideMock}
ignoreClass="ignore-me"
>
<div data-testid="inside">Inside</div>
</ClickOutsideWrapper>
<div className="not-ignored" data-testid="not-ignored">
Not Ignored
</div>
</div>,
);
const notIgnoredElement = container.querySelector(
'[data-testid="not-ignored"]',
) as HTMLElement;
fireEvent.mouseDown(notIgnoredElement);
expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
});
it("uses containerRef when provided", () => {
const containerRef = React.createRef<HTMLDivElement>();
render(
<ClickOutsideWrapper
onClickOutside={onClickOutsideMock}
containerRef={containerRef}
>
<div>Content</div>
</ClickOutsideWrapper>,
);
expect(containerRef.current).toBeTruthy();
expect(containerRef.current?.tagName).toBe("DIV");
});
it("handles composedPath events (e.g. shadow DOM)", () => {
render(
<div>
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div data-testid="inside">Inside</div>
</ClickOutsideWrapper>
</div>,
);
const outsideNode = document.createElement("div");
document.body.appendChild(outsideNode);
const event = new MouseEvent("mousedown", {
bubbles: true,
composed: true,
});
Object.defineProperty(event, "composed", { value: true });
Object.defineProperty(event, "composedPath", {
value: () => [outsideNode, document.body],
});
outsideNode.dispatchEvent(event);
expect(onClickOutsideMock).toHaveBeenCalled();
document.body.removeChild(outsideNode);
});
it("cleans up event listener on unmount", () => {
const removeEventListenerSpy = jest.spyOn(document, "removeEventListener");
const { unmount } = render(
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div>Content</div>
</ClickOutsideWrapper>,
);
unmount();
expect(removeEventListenerSpy).toHaveBeenCalledWith(
"mousedown",
expect.any(Function),
);
removeEventListenerSpy.mockRestore();
});
it("invokes handler registered on document with composedPath target", () => {
const addEventListenerSpy = jest.spyOn(document, "addEventListener");
const removeEventListenerSpy = jest.spyOn(document, "removeEventListener");
const { unmount } = render(
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div>Inside</div>
</ClickOutsideWrapper>,
);
const handlerEntry = addEventListenerSpy.mock.calls.find(
([type]) => type === "mousedown",
);
const handler = handlerEntry?.[1] as EventListener;
const outsideNode = document.createElement("div");
const mockEvent = {
composed: true,
composedPath: () => [outsideNode],
target: outsideNode,
} as unknown as MouseEvent;
handler(mockEvent);
expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
unmount();
addEventListenerSpy.mockRestore();
removeEventListenerSpy.mockRestore();
});
it("falls back to event.target when composedPath does not return nodes", () => {
const addEventListenerSpy = jest.spyOn(document, "addEventListener");
render(
<ClickOutsideWrapper onClickOutside={onClickOutsideMock}>
<div>Inside</div>
</ClickOutsideWrapper>,
);
const handlerEntry = addEventListenerSpy.mock.calls.find(
([type]) => type === "mousedown",
);
const handler = handlerEntry?.[1] as EventListener;
const outsideNode = document.createElement("div");
const mockEvent = {
composed: true,
composedPath: () => [{}],
target: outsideNode,
} as unknown as MouseEvent;
handler(mockEvent);
expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
addEventListenerSpy.mockRestore();
});
it("does not treat non-HTMLElement targets as ignored elements", () => {
const addEventListenerSpy = jest.spyOn(document, "addEventListener");
render(
<ClickOutsideWrapper
onClickOutside={onClickOutsideMock}
ignoreClass="ignore-me"
>
<div>Inside</div>
</ClickOutsideWrapper>,
);
const handlerEntry = addEventListenerSpy.mock.calls.find(
([type]) => type === "mousedown",
);
const handler = handlerEntry?.[1] as EventListener;
const textNode = document.createTextNode("outside");
const mockEvent = {
composed: false,
target: textNode,
} as unknown as MouseEvent;
handler(mockEvent);
expect(onClickOutsideMock).toHaveBeenCalledTimes(1);
addEventListenerSpy.mockRestore();
});
});
@@ -0,0 +1,95 @@
import { render, fireEvent } from "@testing-library/react";
import React from "react";
import CustomInput from "./helper_components/custom_input";
import CustomTimeInput from "./helper_components/custom_time_input";
describe("CustomInput", () => {
it("should call onChange when input value changes", () => {
const onChange = jest.fn();
const { container } = render(<CustomInput onChange={onChange} />);
const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "test value" } });
// Line 22: onChange is called
expect(onChange).toHaveBeenCalled();
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "test value");
});
it("should handle onChange without onChangeArgs", () => {
const onChange = jest.fn();
const { container } = render(<CustomInput onChange={onChange} />);
const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "hello" } });
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "hello");
});
it("should use onChangeArgs when provided", () => {
const onChange = jest.fn();
const onChangeArgs = (
event: React.ChangeEvent<HTMLInputElement>,
): [React.ChangeEvent<HTMLInputElement>, string] => {
return [event, `modified: ${event.target.value}`];
};
const { container } = render(
<CustomInput onChange={onChange} onChangeArgs={onChangeArgs} />,
);
const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "test" } });
// Lines 19-20: onChangeArgs is used
expect(onChange).toHaveBeenCalledWith(expect.any(Object), "modified: test");
});
it("should not throw when onChange is not provided", () => {
const { container } = render(<CustomInput />);
const input = container.querySelector("input") as HTMLInputElement;
expect(() =>
fireEvent.change(input, { target: { value: "test" } }),
).not.toThrow();
});
it("should render input element", () => {
const { container } = render(<CustomInput />);
const input = container.querySelector("input");
expect(input).not.toBeNull();
});
});
describe("CustomTimeInput", () => {
it("should call onChange when time input value changes", () => {
const onChange = jest.fn();
const { container } = render(<CustomTimeInput onChange={onChange} />);
const input = container.querySelector("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "12:30" } });
// Line 20: onChange is called
expect(onChange).toHaveBeenCalled();
});
it("should not throw when onChange is not provided", () => {
const { container } = render(<CustomTimeInput />);
const input = container.querySelector("input") as HTMLInputElement;
expect(() =>
fireEvent.change(input, { target: { value: "10:00" } }),
).not.toThrow();
});
it("should render input element", () => {
const { container } = render(<CustomTimeInput />);
const input = container.querySelector("input");
expect(input).not.toBeNull();
});
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+54
View File
@@ -0,0 +1,54 @@
import { render } from "@testing-library/react";
import { addDays, subDays } from "date-fns";
import React from "react";
import DatePicker from "../index";
describe("DatePicker", () => {
const today = new Date();
// otherDate must be in same month, otherwise it will not be shown on the calendar
const otherDate =
today.getDate() === 1 ? addDays(today, 1) : subDays(today, 1);
const excludeDates = [today, otherDate];
const excludeDatesWithMessages = [
{ date: otherDate, message: "This day is excluded" },
{ date: today, message: "Today is excluded" },
];
it("should disable dates specified in excludeDates props", () => {
const { container: datePicker } = render(
<DatePicker
open
excludeDates={excludeDates}
placeholderText="Select a date other than today or yesterday"
/>,
);
const disabledTimeItems = datePicker.querySelectorAll(
".react-datepicker__day--excluded",
);
expect(disabledTimeItems.length).toBe(excludeDates.length);
});
it("should disable dates specified in excludeDates props and should show the reason", () => {
const { container: datePicker } = render(
<DatePicker
open
excludeDates={excludeDatesWithMessages}
placeholderText="Select a date other than today or yesterday"
/>,
);
const disabledTimeItems = datePicker.querySelectorAll(
".react-datepicker__day--excluded",
);
expect(disabledTimeItems.length).toBe(excludeDatesWithMessages.length);
expect(
disabledTimeItems[today < otherDate ? 1 : 0]?.getAttribute("title"),
).toBe("This day is excluded");
expect(
disabledTimeItems[today < otherDate ? 0 : 1]?.getAttribute("title"),
).toBe("Today is excluded");
});
});
@@ -0,0 +1,30 @@
import { render } from "@testing-library/react";
import React from "react";
import { newDate, setTime } from "../date_utils";
import { setupMockResizeObserver } from "./test_utils";
import DatePicker from "../index";
describe("DatePicker", () => {
beforeAll(() => {
setupMockResizeObserver();
});
it("should only display times between minTime and maxTime", () => {
const now = newDate();
const { container } = render(
<DatePicker
showTimeSelect
selected={now}
onChange={() => null}
minTime={setTime(now, { hour: 17, minute: 0 })}
maxTime={setTime(now, { hour: 18, minute: 0 })}
open
/>,
);
const times = container.querySelector(
"li.react-datepicker__time-list-item",
);
expect(times).not.toBeNull();
});
});
@@ -0,0 +1,50 @@
import { render } from "@testing-library/react";
import React from "react";
import { setTime, newDate } from "../date_utils";
import { setupMockResizeObserver } from "./test_utils";
import DatePicker from "../index";
describe("DatePicker", () => {
let now: Date, excludeTimes: Date[];
beforeAll(() => {
setupMockResizeObserver();
});
beforeEach(() => {
now = newDate();
excludeTimes = [
setTime(now, { hour: 17, minute: 0 }),
setTime(now, { hour: 18, minute: 30 }),
setTime(now, { hour: 19, minute: 30 }),
setTime(now, { hour: 17, minute: 30 }),
];
});
it("should disable times specified in excludeTimes props", () => {
const { container: datePicker } = render(
<DatePicker open showTimeSelect excludeTimes={excludeTimes} />,
);
const disabledTimeItems = datePicker.querySelectorAll(
".react-datepicker__time-list-item--disabled",
);
expect(disabledTimeItems.length).toBe(excludeTimes.length);
});
it("should add aria-disabled to all the excluded times", () => {
const { container: datePicker } = render(
<DatePicker open showTimeSelect excludeTimes={excludeTimes} />,
);
const disabledTimeItems = datePicker.querySelectorAll(
".react-datepicker__time-list-item--disabled",
);
const allDisabledTimeItemsHaveAriaDisabled = Array.from(
disabledTimeItems,
).every((time) => time.getAttribute("aria-disabled") === "true");
expect(allDisabledTimeItemsHaveAriaDisabled).toBe(true);
});
});
@@ -0,0 +1,140 @@
import { fireEvent, render } from "@testing-library/react";
import React from "react";
import { getHours } from "../date_utils";
import TimeComponent from "../time";
describe("TimeComponent", () => {
const HOUR_TO_DISABLE_IN_12_HR = 5;
const HOUR_TO_DISABLE_IN_24_HR = 17;
it("should disable times matched by filterTime prop", () => {
const { container: timeComponent } = render(
<TimeComponent
filterTime={(time) => getHours(time) !== HOUR_TO_DISABLE_IN_24_HR}
/>,
);
const disabledTimeItems = timeComponent.querySelectorAll(
".react-datepicker__time-list-item--disabled",
);
const disabledAllFilterTimes = Array.from(disabledTimeItems).every(
(disabledTimeItem) => {
const disabledTimeItemValue = (
disabledTimeItem.textContent ?? ""
).trim();
return (
disabledTimeItemValue.startsWith(`${HOUR_TO_DISABLE_IN_12_HR}:`) ||
disabledTimeItemValue.startsWith(`${HOUR_TO_DISABLE_IN_24_HR}:`)
);
},
);
expect(disabledAllFilterTimes).toBe(true);
});
it("should add aria-disabled to the disabled times matched by filterTime prop", () => {
const { container: timeComponent } = render(
<TimeComponent
filterTime={(time) => getHours(time) !== HOUR_TO_DISABLE_IN_24_HR}
/>,
);
const disabledTimeItems = timeComponent.querySelectorAll(
".react-datepicker__time-list-item--disabled",
);
const allDisabledTimeItemsHaveAriaDisabled = Array.from(
disabledTimeItems,
).every((time) => time.getAttribute("aria-disabled") === "true");
expect(allDisabledTimeItemsHaveAriaDisabled).toBe(true);
});
it("should block onChange for disabled times", () => {
const onChange = jest.fn();
const { container } = render(
<TimeComponent
onChange={onChange}
filterTime={(time) => getHours(time) !== HOUR_TO_DISABLE_IN_24_HR}
/>,
);
const disabledTime = Array.from(
container.querySelectorAll(".react-datepicker__time-list-item"),
).find((node) =>
node.classList.contains("react-datepicker__time-list-item--disabled"),
) as HTMLElement;
fireEvent.click(disabledTime);
expect(onChange).not.toHaveBeenCalled();
});
it("should call onChange for enabled times", () => {
const onChange = jest.fn();
const { container } = render(
<TimeComponent onChange={onChange} filterTime={() => true} />,
);
const enabledTime = Array.from(
container.querySelectorAll(".react-datepicker__time-list-item"),
).find(
(node) =>
!node.classList.contains("react-datepicker__time-list-item--disabled"),
) as HTMLElement;
fireEvent.click(enabledTime);
expect(onChange).toHaveBeenCalled();
});
it("should prevent clicks outside the provided min/max time range", () => {
const onChange = jest.fn();
const minTime = new Date("2024-01-01T08:00:00");
const maxTime = new Date("2024-01-01T10:00:00");
const { container } = render(
<TimeComponent
onChange={onChange}
minTime={minTime}
maxTime={maxTime}
selected={new Date("2024-01-01T07:00:00")}
/>,
);
const disabledSlot = Array.from(
container.querySelectorAll(".react-datepicker__time-list-item"),
).find((node) =>
node.classList.contains("react-datepicker__time-list-item--disabled"),
) as HTMLElement;
fireEvent.click(disabledSlot);
expect(onChange).not.toHaveBeenCalled();
});
it("should allow clicks within the min/max time range", () => {
const onChange = jest.fn();
const minTime = new Date("2024-01-01T08:00:00");
const maxTime = new Date("2024-01-01T10:00:00");
const { container } = render(
<TimeComponent
onChange={onChange}
minTime={minTime}
maxTime={maxTime}
selected={new Date("2024-01-01T08:30:00")}
/>,
);
const enabledSlot = Array.from(
container.querySelectorAll(".react-datepicker__time-list-item"),
).find(
(node) =>
!node.classList.contains("react-datepicker__time-list-item--disabled"),
) as HTMLElement;
fireEvent.click(enabledSlot);
expect(onChange).toHaveBeenCalled();
});
});
@@ -0,0 +1,31 @@
import React from "react";
type Props = React.ComponentPropsWithoutRef<"svg">;
export const IconParkSolidApplication: React.FC<Props> = (props) => {
return (
<svg
data-testid="icon-park-solid-application"
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
{...props}
>
<mask id="ipSApplication0">
<g fill="none" stroke="#fff" strokeLinejoin="round" strokeWidth="4">
<path strokeLinecap="round" d="M40.04 22v20h-32V22" />
<path
fill="#fff"
d="M5.842 13.777C4.312 17.737 7.263 22 11.51 22c3.314 0 6.019-2.686 6.019-6a6 6 0 0 0 6 6h1.018a6 6 0 0 0 6-6c0 3.314 2.706 6 6.02 6c4.248 0 7.201-4.265 5.67-8.228L39.234 6H8.845l-3.003 7.777Z"
/>
</g>
</mask>
<path
fill="currentColor"
d="M0 0h48v48H0z"
mask="url(#ipSApplication0)"
/>
</svg>
);
};
@@ -0,0 +1,32 @@
import React from "react";
type Props = {
onChange?: (
event: React.ChangeEvent<HTMLInputElement>,
value: string,
) => void;
onChangeArgs?: (
event: React.ChangeEvent<HTMLInputElement>,
) => [React.ChangeEvent<HTMLInputElement>, string];
};
class CustomInput extends React.Component<Props> {
onChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
let args: [React.ChangeEvent<HTMLInputElement>, string] = [
event,
event.target.value,
];
if (this.props.onChangeArgs) {
args = this.props.onChangeArgs(event);
}
this.props.onChange?.apply(this, args);
};
render() {
const { ...props } = this.props;
delete props.onChangeArgs;
return <input {...props} onChange={this.onChange} />;
}
}
export default CustomInput;
@@ -0,0 +1,27 @@
import React from "react";
type Props = {
date?: Date;
value?: string;
onChange?: (date: string | undefined) => void;
onTimeChange?: (value: Date | undefined) => void;
};
const CustomTimeInput: React.FC<Props> = ({
onChange,
onTimeChange,
date,
value,
...restProps
}) => (
<input
value={value}
onChange={(e) =>
onTimeChange ? onTimeChange(date) : onChange?.(e.target.value)
}
style={{ border: "solid 1px pink" }}
{...restProps}
/>
);
export default CustomTimeInput;
@@ -0,0 +1,31 @@
import React, {
type FC,
type PropsWithChildren,
useCallback,
useState,
} from "react";
import { createPortal } from "react-dom";
const ShadowRoot: FC<PropsWithChildren> = ({ children }) => {
const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);
const containerRefCallback = useCallback(
(container: HTMLDivElement | null) => {
if (!container) {
return;
}
const root =
container.shadowRoot ?? container.attachShadow({ mode: "open" });
setShadowRoot(root);
},
[],
);
return (
<div ref={containerRefCallback}>
{shadowRoot && createPortal(children, shadowRoot)}
</div>
);
};
export default ShadowRoot;
@@ -0,0 +1,12 @@
import { clsx } from "clsx";
import React from "react";
type Props = React.PropsWithChildren<{
className?: string;
}>;
const TestWrapper: React.FC<Props> = ({ className, children }) => (
<div className={clsx("test-wrapper", className)}>{children}</div>
);
export default TestWrapper;
@@ -0,0 +1,89 @@
import { render } from "@testing-library/react";
import React from "react";
import {
addHours,
addMinutes,
addSeconds,
getStartOfDay,
newDate,
} from "../date_utils";
import TimeComponent from "../time";
describe("TimeComponent", () => {
let today: Date, includeTimes: Date[];
beforeEach(() => {
today = getStartOfDay(newDate());
includeTimes = [
addMinutes(today, 60),
addMinutes(today, 120),
addMinutes(today, 150),
];
});
it("should only enable times specified in includeTimes props", () => {
const { container: timeComponent } = render(
<TimeComponent includeTimes={includeTimes} />,
);
const allTimeItems = timeComponent.querySelectorAll(
".react-datepicker__time-list-item",
);
const disabledTimeItems = timeComponent.querySelectorAll(
".react-datepicker__time-list-item--disabled",
);
const expectedDisabledTimeItems = allTimeItems.length - includeTimes.length;
expect(disabledTimeItems.length).toBe(expectedDisabledTimeItems);
});
it("should not add aria-disabled attribute on all the enabled times", () => {
const { container: timeComponent } = render(
<TimeComponent includeTimes={includeTimes} />,
);
const allTimeItems = timeComponent.querySelectorAll(
".react-datepicker__time-list-item",
);
const enabledTimeItems = Array.from(allTimeItems).filter(
(timeItem) =>
!timeItem.classList.contains(
"react-datepicker__time-list-item--disabled",
),
);
const enabledTimeItemsHasNoAriaDisabled = Array.from(
enabledTimeItems,
).every((timeItem) => {
const ariaDisabledValue = timeItem.getAttribute("aria-disabled");
return !ariaDisabledValue || ariaDisabledValue.toLowerCase() === "false";
});
expect(enabledTimeItemsHasNoAriaDisabled).toBe(true);
});
it("should factor in seconds", () => {
const includeHoursMinutesSeconds = [
addHours(addSeconds(today, 30), 1), //01:00:30
addSeconds(today, 30), //00:00:30
];
const { container: timeComponent } = render(
<TimeComponent
format="HH:mm:ss"
includeTimes={includeHoursMinutesSeconds}
/>,
);
const disabledTimeItems = timeComponent.querySelectorAll(
".react-datepicker__time-list-item--disabled",
);
// 01:00:00 and 00:00:00 should be correctly disabled because they are not included
expect(
Array.from(disabledTimeItems).map((node) => node.textContent),
).toContain("01:00:00");
expect(
Array.from(disabledTimeItems).map((node) => node.textContent),
).toContain("00:00:00");
});
});
+36
View File
@@ -0,0 +1,36 @@
import "jest-canvas-mock";
import { toHaveNoViolations } from "jest-axe";
expect.extend(toHaveNoViolations);
// Suppress act() warnings from floating-ui library
const originalError = console.error;
beforeAll(() => {
console.error = (...args) => {
// Convert all arguments to a single string for checking
const fullMessage = args
.map((arg) =>
typeof arg === "string"
? arg
: arg instanceof Error
? arg.message
: String(arg),
)
.join(" ");
// Suppress floating-ui act warnings - these come from @floating-ui/react-dom
// internally using flushSync, which is expected behavior
if (
fullMessage.includes("withFloating(PopperComponent)") &&
fullMessage.includes("not wrapped in act")
) {
return;
}
originalError.call(console, ...args);
};
});
afterAll(() => {
console.error = originalError;
});
@@ -0,0 +1,119 @@
import { render } from "@testing-library/react";
import React from "react";
import {
addHours,
addMinutes,
addSeconds,
getStartOfDay,
newDate,
} from "../date_utils";
import TimeComponent from "../time";
describe("TimeComponent", () => {
it("should show times specified in injectTimes props", () => {
const today = getStartOfDay(newDate());
const { container } = render(
<TimeComponent
injectTimes={[
addMinutes(today, 1),
addMinutes(today, 725),
addMinutes(today, 1439),
]}
/>,
);
const injectedItems = container.querySelectorAll(
".react-datepicker__time-list-item--injected",
);
expect(injectedItems).toHaveLength(3);
});
it("should not affect existing time intervals", () => {
const today = getStartOfDay(newDate());
const { container } = render(
<TimeComponent
intervals={60}
injectTimes={[
addMinutes(today, 0),
addMinutes(today, 60),
addMinutes(today, 1440),
]}
/>,
);
const injectedItems = container.querySelectorAll(
".react-datepicker__time-list-item--injected",
);
expect(injectedItems).toHaveLength(0);
});
it("should allow multiple injected times per interval", () => {
const today = getStartOfDay(newDate());
const { container } = render(
<TimeComponent
intervals={60}
injectTimes={[
addMinutes(today, 1),
addMinutes(today, 2),
addMinutes(today, 3),
]}
/>,
);
const injectedItems = container.querySelectorAll(
".react-datepicker__time-list-item--injected",
);
expect(injectedItems).toHaveLength(3);
});
it("should sort injected times automatically", () => {
const today = getStartOfDay(newDate());
const { container } = render(
<TimeComponent
intervals={60}
injectTimes={[
addMinutes(today, 3),
addMinutes(today, 1),
addMinutes(today, 2),
]}
/>,
);
const injectedItems = container.querySelectorAll(
".react-datepicker__time-list-item--injected",
);
expect(Array.from(injectedItems).map((node) => node.textContent)).toEqual([
"12:01 AM",
"12:02 AM",
"12:03 AM",
]);
});
it("should support hours, minutes, and seconds", () => {
const today = getStartOfDay(newDate());
const { container } = render(
<TimeComponent
format="HH:mm:ss"
intervals={60}
injectTimes={[
addSeconds(today, 1),
addMinutes(addSeconds(today, 1), 30),
addHours(addMinutes(addSeconds(today, 1), 30), 1),
]}
/>,
);
const injectedItems = container.querySelectorAll(
".react-datepicker__time-list-item--injected",
);
expect(Array.from(injectedItems).map((node) => node.textContent)).toEqual([
"00:00:01",
"00:30:01",
"01:30:01",
]);
});
});
+267
View File
@@ -0,0 +1,267 @@
import { render, fireEvent } from "@testing-library/react";
import React from "react";
import InputTime from "../input_time";
import CustomTimeInput from "./helper_components/custom_time_input";
describe("InputTime", () => {
it("renders with default props", () => {
const { container } = render(<InputTime />);
const timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
expect(timeInput).toBeTruthy();
expect(timeInput.className).toBe("react-datepicker-time__input");
expect(timeInput.placeholder).toBe("Time");
});
it("renders with timeString prop", () => {
const { container } = render(<InputTime timeString="14:30" />);
const timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
expect(timeInput.value).toBe("14:30");
});
it("renders with timeInputLabel prop", () => {
const { container } = render(<InputTime timeInputLabel="Select Time" />);
const label = container.querySelector(
".react-datepicker-time__caption",
) as HTMLElement;
expect(label.textContent).toBe("Select Time");
});
it("calls onChange when time is changed", () => {
const onChangeMock = jest.fn();
const { container } = render(
<InputTime onChange={onChangeMock} timeString="10:00" />,
);
const timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
fireEvent.change(timeInput, { target: { value: "15:45" } });
expect(onChangeMock).toHaveBeenCalledTimes(1);
const calledDate = onChangeMock.mock.calls[0][0];
expect(calledDate.getHours()).toBe(15);
expect(calledDate.getMinutes()).toBe(45);
});
it("updates state when timeString prop changes", () => {
const { container, rerender } = render(<InputTime timeString="10:00" />);
let timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
expect(timeInput.value).toBe("10:00");
rerender(<InputTime timeString="16:30" />);
timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
expect(timeInput.value).toBe("16:30");
});
it("uses provided date when onChange is called", () => {
const onChangeMock = jest.fn();
const testDate = new Date(2023, 5, 15, 10, 30);
const { container } = render(
<InputTime onChange={onChangeMock} date={testDate} timeString="10:30" />,
);
const timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
fireEvent.change(timeInput, { target: { value: "14:45" } });
expect(onChangeMock).toHaveBeenCalledTimes(1);
const calledDate = onChangeMock.mock.calls[0][0];
expect(calledDate.getFullYear()).toBe(2023);
expect(calledDate.getMonth()).toBe(5);
expect(calledDate.getDate()).toBe(15);
expect(calledDate.getHours()).toBe(14);
expect(calledDate.getMinutes()).toBe(45);
});
it("creates new date when no date prop is provided", () => {
const onChangeMock = jest.fn();
const { container } = render(
<InputTime onChange={onChangeMock} timeString="10:00" />,
);
const timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
fireEvent.change(timeInput, { target: { value: "14:45" } });
expect(onChangeMock).toHaveBeenCalledTimes(1);
const calledDate = onChangeMock.mock.calls[0][0];
expect(calledDate).toBeInstanceOf(Date);
expect(calledDate.getHours()).toBe(14);
expect(calledDate.getMinutes()).toBe(45);
});
it("renders custom time input when provided", () => {
const CustomTimeInput = ({
value,
onChange,
}: {
value: string;
onChange: (time: string) => void;
}) => (
<input
data-testid="custom-time-input"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
const { container } = render(
<InputTime
customTimeInput={<CustomTimeInput value="" onChange={() => {}} />}
timeString="12:00"
/>,
);
const customInput = container.querySelector(
'[data-testid="custom-time-input"]',
) as HTMLInputElement;
expect(customInput).toBeTruthy();
expect(customInput.value).toBe("12:00");
});
it("calls onChange with custom time input", () => {
const onChangeMock = jest.fn();
const CustomTimeInput = ({
value,
onChange,
}: {
value: string;
onChange: (time: string) => void;
}) => (
<input
data-testid="custom-time-input"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
const { container } = render(
<InputTime
onChange={onChangeMock}
customTimeInput={<CustomTimeInput value="" onChange={() => {}} />}
timeString="12:00"
/>,
);
const customInput = container.querySelector(
'[data-testid="custom-time-input"]',
) as HTMLInputElement;
fireEvent.change(customInput, { target: { value: "18:30" } });
expect(onChangeMock).toHaveBeenCalledTimes(1);
const calledDate = onChangeMock.mock.calls[0][0];
expect(calledDate.getHours()).toBe(18);
expect(calledDate.getMinutes()).toBe(30);
});
it("focuses input when clicked", () => {
const { container } = render(<InputTime timeString="10:00" />);
const timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
const focusSpy = jest.spyOn(timeInput, "focus");
fireEvent.click(timeInput);
expect(focusSpy).toHaveBeenCalled();
focusSpy.mockRestore();
});
it("uses timeString as fallback when onChange value is empty", () => {
const onChangeMock = jest.fn();
const { container } = render(
<InputTime onChange={onChangeMock} timeString="10:00" />,
);
const timeInput = container.querySelector(
'input[type="time"]',
) as HTMLInputElement;
fireEvent.change(timeInput, { target: { value: "" } });
expect(onChangeMock).toHaveBeenCalledTimes(1);
expect(timeInput.value).toBe("10:00");
});
it("passes provided date through customTimeInput onTimeChange handler", () => {
const onTimeChange = jest.fn();
const date = new Date("2023-09-30T10:00:00");
const { container } = render(
<InputTime
date={date}
timeString="10:00"
customTimeInput={
<CustomTimeInput
data-testid="custom-time-input"
onTimeChange={onTimeChange}
/>
}
/>,
);
const customInput = container.querySelector(
'[data-testid="custom-time-input"]',
) as HTMLInputElement;
fireEvent.change(customInput, { target: { value: "11:15" } });
expect(onTimeChange).toHaveBeenCalledWith(date);
});
it("preserves existing time when custom input emits value without colon", () => {
const onChange = jest.fn();
const date = new Date("2023-09-30T11:00:00");
const { container } = render(
<InputTime
date={date}
timeString="11:00"
onChange={onChange}
customTimeInput={<CustomTimeInput data-testid="partial-input" />}
/>,
);
const customInput = container.querySelector(
'[data-testid="partial-input"]',
) as HTMLInputElement;
fireEvent.change(customInput, { target: { value: "invalid" } });
expect(onChange).toHaveBeenCalledTimes(1);
const calledDate = onChange.mock.calls[0][0];
expect(calledDate.getHours()).toBe(11);
expect(calledDate.getMinutes()).toBe(0);
});
it("renders container with correct class names", () => {
const { container } = render(<InputTime />);
expect(
container.querySelector(".react-datepicker__input-time-container"),
).toBeTruthy();
expect(
container.querySelector(".react-datepicker-time__input-container"),
).toBeTruthy();
expect(
container.querySelector(".react-datepicker-time__input"),
).toBeTruthy();
});
});
+94
View File
@@ -0,0 +1,94 @@
import { fireEvent, render } from "@testing-library/react";
import React, { useState } from "react";
import DatePicker from "../index";
import { safeQuerySelector, setupMockResizeObserver } from "./test_utils";
import type { DatePickerProps } from "../index";
// see https://github.com/microsoft/TypeScript/issues/31501
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type OmitUnion<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
const DatePickerWithState = (
props: Partial<
Pick<DatePickerProps, "open" | "selected" | "showTimeSelect" | "dateFormat">
> &
OmitUnion<
DatePickerProps,
| "open"
| "selected"
| "onChange"
| "showTimeSelect"
| "dateFormat"
| "selectsRange"
| "selectsMultiple"
| "formatMultipleDates"
| "onSelect"
>,
) => {
const [selected, setSelected] = useState<Date | null>(null);
return (
<DatePicker
open
selected={selected}
onChange={(date: Date | null) => {
setSelected(date);
}}
showTimeSelect
dateFormat="MM/dd/yyyy HH:mm"
onSelect={() => {}}
{...props}
/>
);
};
describe("Datepicker minTime", () => {
beforeAll(() => {
setupMockResizeObserver();
});
it("should select time 12:00 AM when no minTime constraint is set.", () => {
const { getByText, container } = render(<DatePickerWithState />);
const day = container.getElementsByClassName("react-datepicker__day")[0]!;
fireEvent.click(day);
const selectedTime = getByText("12:00 AM");
expect(selectedTime.getAttribute("aria-selected")).toBe("true");
});
it("should select the minimum allowable time upon choosing a day.", () => {
const minTime = new Date("2023-03-10 13:00");
const maxTime = new Date("2023-03-10 18:00");
const { container, getByText } = render(
<DatePickerWithState minTime={minTime} maxTime={maxTime} />,
);
const day = container.getElementsByClassName("react-datepicker__day")[0]!;
fireEvent.click(day);
const selectedTime = getByText("1:00 PM");
expect(selectedTime.getAttribute("aria-selected")).toBe("true");
});
it("should select time from input instead of minimum allowable time when time is typed in", () => {
const minTime = new Date("2023-03-10 13:00");
const maxTime = new Date("2023-03-10 18:00");
const { container } = render(
<DatePickerWithState minTime={minTime} maxTime={maxTime} />,
);
const input = safeQuerySelector<HTMLInputElement>(container, "input");
fireEvent.change(input, { target: { value: "03/10/2023 16:00" } });
fireEvent.focusOut(input);
expect(input.value).toEqual("03/10/2023 16:00");
});
});
@@ -0,0 +1,385 @@
import { render, fireEvent } from "@testing-library/react";
import { el } from "date-fns/locale/el";
import { ru } from "date-fns/locale/ru";
import { zhCN } from "date-fns/locale/zh-CN";
import React from "react";
import { getMonthInLocale, registerLocale } from "../date_utils";
import MonthDropdown from "../month_dropdown";
import MonthDropdownOptions from "../month_dropdown_options";
import { range, safeQuerySelector, safeQuerySelectorAll } from "./test_utils";
type MonthDropdownProps = React.ComponentProps<typeof MonthDropdown>;
describe("MonthDropdown", () => {
let monthDropdown: HTMLElement;
let handleChangeResult: number | null;
const mockHandleChange = function (changeInput: number) {
handleChangeResult = changeInput;
};
function getMonthDropdown(
overrideProps?: Partial<
Pick<MonthDropdownProps, "dropdownMode" | "month" | "onChange">
> &
Omit<MonthDropdownProps, "dropdownMode" | "month" | "onChange">,
) {
return render(
<MonthDropdown
dropdownMode="scroll"
month={11}
onChange={mockHandleChange}
{...overrideProps}
/>,
).container;
}
beforeEach(() => {
handleChangeResult = null;
});
describe("scroll mode", () => {
beforeEach(() => {
monthDropdown = getMonthDropdown();
});
it("shows the selected month in the initial view", () => {
expect(monthDropdown?.textContent).toContain("December");
});
it("opens a list when read view is clicked", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const optionsView = monthDropdown?.querySelector(
".react-datepicker__month-dropdown",
);
expect(optionsView).not.toBeNull();
});
describe("with the selected month", () => {
let selectedMonth: HTMLSelectElement | null | undefined;
beforeEach(() => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
selectedMonth = monthDropdown?.querySelector<HTMLSelectElement>(
".react-datepicker__month-option--selected_month",
);
});
it("applies the 'selected' modifier class to the selected month", () => {
expect(selectedMonth?.textContent).toContain("December");
});
it("adds aria-selected property to the selected month", () => {
const ariaSelected = selectedMonth?.getAttribute("aria-selected");
expect(ariaSelected).toEqual("true");
});
});
describe("with a not selected month", () => {
let notSelectedMonth: HTMLElement | null | undefined;
beforeEach(() => {
fireEvent.click(
monthDropdown?.querySelector(".react-datepicker__month-read-view") ??
new HTMLSelectElement(),
);
notSelectedMonth = safeQuerySelector(
monthDropdown,
".react-datepicker__month-option",
);
});
it("does not apply the 'selected' modifier class to the selected month", () => {
expect(notSelectedMonth?.textContent).not.toContain("December");
});
it("does not add aria-selected property to the selected month", () => {
const ariaSelected = notSelectedMonth?.getAttribute("aria-selected");
expect(ariaSelected).toBeNull();
});
});
it("closes the dropdown when a month is clicked", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const minMonthOptionsLen = 2;
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
minMonthOptionsLen,
);
fireEvent.click(monthOptions[1]!);
expect(
monthDropdown?.querySelectorAll(".react-datepicker__month-dropdown"),
).toHaveLength(0);
});
it("closes the dropdown if outside is clicked", () => {
const monthNames = range(0, 12).map((M) => getMonthInLocale(M));
const onCancelSpy = jest.fn();
render(
<MonthDropdownOptions
onCancel={onCancelSpy}
onChange={onCancelSpy}
month={11}
monthNames={monthNames}
/>,
);
fireEvent.mouseDown(document.body);
fireEvent.touchStart(document.body);
expect(onCancelSpy).toHaveBeenCalledTimes(1);
});
it("does not call the supplied onChange function when the same month is clicked", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const monthOptionsLen = 12;
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
monthOptionsLen,
);
fireEvent.click(monthOptions[11]!);
expect(handleChangeResult).toBeNull();
});
it("calls the supplied onChange function when a different month is clicked", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const minRequiredMonthsLen = 3;
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
minRequiredMonthsLen,
);
fireEvent.click(monthOptions[2]!);
expect(handleChangeResult).toEqual(2);
});
it("should use locale stand-alone formatting to display month names", () => {
registerLocale("el", el);
registerLocale("ru", ru);
let dropdownDateFormat = getMonthDropdown();
expect(dropdownDateFormat.textContent).toContain("December");
dropdownDateFormat = getMonthDropdown({ locale: "el" });
expect(dropdownDateFormat.textContent).toContain("Δεκέμβριος");
dropdownDateFormat = getMonthDropdown({ locale: "ru" });
expect(dropdownDateFormat.textContent).toContain("декабрь");
});
it("calls the supplied onChange function when a month is selected using arrows and enter key", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
);
const monthOption = monthOptions[3]!;
fireEvent.keyDown(monthOption, { key: "ArrowDown" });
const nextMonthOption = monthOptions[4];
expect(document.activeElement).toEqual(nextMonthOption);
fireEvent.keyDown(document.activeElement!, { key: "Enter" });
expect(handleChangeResult).toEqual(4);
});
it("handles ArrowUp key navigation correctly", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
);
const monthOption = monthOptions[5]!;
fireEvent.keyDown(monthOption, { key: "ArrowUp" });
const prevMonthOption = monthOptions[4];
expect(document.activeElement).toEqual(prevMonthOption);
});
it("handles Escape key to cancel dropdown", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
);
const monthOption = monthOptions[5]!;
fireEvent.keyDown(monthOption, { key: "Escape" });
expect(
monthDropdown?.querySelectorAll(".react-datepicker__month-dropdown"),
).toHaveLength(0);
});
it("wraps around when using ArrowUp on first month", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
);
const firstMonthOption = monthOptions[0]!;
fireEvent.keyDown(firstMonthOption, { key: "ArrowUp" });
const lastMonthOption = monthOptions[11];
expect(document.activeElement).toEqual(lastMonthOption);
});
it("wraps around when using ArrowDown on last month", () => {
const monthReadView = safeQuerySelector(
monthDropdown,
".react-datepicker__month-read-view",
);
fireEvent.click(monthReadView);
const monthOptions = safeQuerySelectorAll(
monthDropdown,
".react-datepicker__month-option",
);
const lastMonthOption = monthOptions[11]!;
fireEvent.keyDown(lastMonthOption, { key: "ArrowDown" });
const firstMonthOption = monthOptions[0];
expect(document.activeElement).toEqual(firstMonthOption);
});
});
describe("select mode", () => {
it("renders a select", () => {
monthDropdown = getMonthDropdown({ dropdownMode: "select" });
const select = monthDropdown.querySelector<HTMLSelectElement>(
".react-datepicker__month-select",
);
expect(select).not.toBeNull();
expect(select?.value).toEqual("11");
const options = select?.querySelectorAll("option");
expect(Array.from(options ?? []).map((o) => Number(o.value))).toEqual(
range(0, 12),
);
});
it("renders month options with default locale", () => {
monthDropdown = getMonthDropdown({ dropdownMode: "select" });
const options = monthDropdown.querySelectorAll("option");
expect(Array.from(options).map((o) => o.textContent)).toEqual([
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
]);
});
// Short Month Names
it("renders month options with short name and default locale", () => {
monthDropdown = getMonthDropdown({
dropdownMode: "select",
useShortMonthInDropdown: true,
});
const options = monthDropdown.querySelectorAll("option");
expect(Array.from(options).map((o) => o.textContent)).toEqual([
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
]);
});
it("renders month options with specified locale", () => {
registerLocale("zh-cn", zhCN);
monthDropdown = getMonthDropdown({
dropdownMode: "select",
locale: "zh-cn",
});
const options = monthDropdown.querySelectorAll("option");
expect(Array.from(options).map((o) => o.textContent)).toEqual([
"一月",
"二月",
"三月",
"四月",
"五月",
"六月",
"七月",
"八月",
"九月",
"十月",
"十一月",
"十二月",
]);
});
it("calls the supplied onChange function when a different month is clicked", () => {
monthDropdown = getMonthDropdown({ dropdownMode: "select", month: 11 });
const select = monthDropdown.querySelector<HTMLSelectElement>(
".react-datepicker__month-select",
);
fireEvent.change(select ?? new HTMLSelectElement(), {
target: { value: 9 },
});
expect(handleChangeResult).toEqual(9);
});
});
});
@@ -0,0 +1,219 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react";
import React from "react";
import Calendar from "../calendar";
import { newDate, formatDate } from "../date_utils";
const dateFormat = "MMMM yyyy";
describe("monthHeaderPosition", () => {
it("should render month header in top position by default", () => {
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
/>,
);
// Header should be in the default header section
const header = container.querySelector(".react-datepicker__header");
const currentMonth = header?.querySelector(
".react-datepicker__current-month",
);
expect(currentMonth).not.toBeNull();
expect(currentMonth?.textContent).toContain(
formatDate(newDate(), dateFormat),
);
});
it("should render month header in middle position when monthHeaderPosition is 'middle'", () => {
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
monthHeaderPosition="middle"
/>,
);
// Header should be within the header-wrapper (not at top of calendar)
const topHeaderOutsideMonths = container.querySelectorAll(
".react-datepicker__month-container > .react-datepicker__header",
);
expect(topHeaderOutsideMonths.length).toBe(0);
// Should be within the month container (middle position)
const monthContainer = container.querySelector(
".react-datepicker__month-container",
);
const headerInMonth = monthContainer?.querySelector(
".react-datepicker__header .react-datepicker__current-month",
);
expect(headerInMonth).not.toBeNull();
expect(headerInMonth?.textContent).toContain(
formatDate(newDate(), dateFormat),
);
// Should have wrapper with navigation buttons
const headerWrapper = container.querySelector(
".react-datepicker__header-wrapper",
);
expect(headerWrapper).not.toBeNull();
});
it("should render month header in bottom position when monthHeaderPosition is 'bottom'", () => {
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
monthHeaderPosition="bottom"
/>,
);
// Header should be within the header-wrapper (not at top of calendar)
const topHeaderOutsideMonths = container.querySelectorAll(
".react-datepicker__month-container > .react-datepicker__header",
);
expect(topHeaderOutsideMonths.length).toBe(0);
// Should be within the month container (bottom position)
const monthContainer = container.querySelector(
".react-datepicker__month-container",
);
const headerInMonth = monthContainer?.querySelector(
".react-datepicker__header .react-datepicker__current-month",
);
expect(headerInMonth).not.toBeNull();
expect(headerInMonth?.textContent).toContain(
formatDate(newDate(), dateFormat),
);
// Should have wrapper with navigation buttons
const headerWrapper = container.querySelector(
".react-datepicker__header-wrapper",
);
expect(headerWrapper).not.toBeNull();
});
it("should render month header for each month when multiple months shown with middle position", () => {
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
monthHeaderPosition="middle"
monthsShown={2}
/>,
);
// Should find headers within header-wrappers
const headerWrappers = container.querySelectorAll(
".react-datepicker__header-wrapper",
);
expect(headerWrappers.length).toBe(2);
const monthHeaders = container.querySelectorAll(
".react-datepicker__header-wrapper .react-datepicker__header .react-datepicker__current-month",
);
expect(monthHeaders.length).toBe(2);
});
it("should render month header for each month when multiple months shown with bottom position", () => {
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
monthHeaderPosition="bottom"
monthsShown={2}
/>,
);
// Should find headers within header-wrappers
const headerWrappers = container.querySelectorAll(
".react-datepicker__header-wrapper",
);
expect(headerWrappers.length).toBe(2);
const monthHeaders = container.querySelectorAll(
".react-datepicker__header-wrapper .react-datepicker__header .react-datepicker__current-month",
);
expect(monthHeaders.length).toBe(2);
});
it("should use top position when monthHeaderPosition is 'top'", () => {
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
monthHeaderPosition="top"
/>,
);
// Header should be in the default header section
const header = container.querySelector(".react-datepicker__header");
const currentMonth = header?.querySelector(
".react-datepicker__current-month",
);
expect(currentMonth).not.toBeNull();
});
it("should render month header with middle position when navigation buttons might be hidden", () => {
const minDate = newDate();
const maxDate = newDate();
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
monthHeaderPosition="middle"
minDate={minDate}
maxDate={maxDate}
showDisabledMonthNavigation={false}
/>,
);
// Should still render the wrapper with header
const headerWrapper = container.querySelector(
".react-datepicker__header-wrapper",
);
expect(headerWrapper).not.toBeNull();
const header = container.querySelector(".react-datepicker__header");
expect(header).not.toBeNull();
});
it("should render month header with bottom position when renderCustomHeader is provided", () => {
const { container } = render(
<Calendar
dateFormat={dateFormat}
onClickOutside={() => {}}
onSelect={() => {}}
dropdownMode="scroll"
monthHeaderPosition="bottom"
renderCustomHeader={() => <div>Custom Header</div>}
/>,
);
// Should render custom header
const customHeader = container.querySelector(
".react-datepicker__header--custom",
);
expect(customHeader).not.toBeNull();
});
});
+75
View File
@@ -0,0 +1,75 @@
import type React from "react";
import Month from "../month";
import { KeyType, newDate } from "../date_utils";
type MonthComponentProps = React.ComponentProps<typeof Month>;
const buildProps = (
override: Partial<MonthComponentProps> = {},
): MonthComponentProps =>
({
day: newDate("2024-01-01"),
onDayClick: jest.fn(),
onDayMouseEnter: jest.fn(),
onMouseLeave: jest.fn(),
setPreSelection: jest.fn(),
preSelection: newDate("2024-01-01"),
showFourColumnMonthYearPicker: false,
showTwoColumnMonthYearPicker: false,
disabledKeyboardNavigation: false,
...override,
}) as MonthComponentProps;
describe("Month logic helpers", () => {
it("short-circuits keyboard navigation when there is no preSelection", () => {
const props = buildProps({ preSelection: undefined });
const instance = new Month(props);
const getVerticalOffsetSpy = jest.spyOn(instance, "getVerticalOffset");
instance.handleKeyboardNavigation(
{
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>,
KeyType.ArrowRight,
1,
);
expect(getVerticalOffsetSpy).not.toHaveBeenCalled();
expect(props.setPreSelection).not.toHaveBeenCalled();
});
it("prevents quarter navigation when the destination date is disabled", () => {
const props = buildProps();
const instance = new Month(props);
jest.spyOn(instance, "isDisabled").mockReturnValue(true);
jest.spyOn(instance, "isExcluded").mockReturnValue(false);
instance.handleQuarterNavigation(2, newDate("2024-04-01"));
expect(props.setPreSelection).not.toHaveBeenCalled();
});
it("does not handle quarter arrow keys without a preSelection value", () => {
const props = buildProps({ preSelection: undefined });
const instance = new Month(props);
const navigationSpy = jest.spyOn(instance, "handleQuarterNavigation");
instance.onQuarterKeyDown(
{
key: KeyType.ArrowRight,
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>,
2,
);
instance.onQuarterKeyDown(
{
key: KeyType.ArrowLeft,
preventDefault: jest.fn(),
} as unknown as React.KeyboardEvent<HTMLDivElement>,
2,
);
expect(navigationSpy).not.toHaveBeenCalled();
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,336 @@
import { render, fireEvent } from "@testing-library/react";
import { fi } from "date-fns/locale/fi";
import React from "react";
import {
newDate,
addMonths,
subMonths,
formatDate,
isAfter,
registerLocale,
} from "../date_utils";
import MonthYearDropdown from "../month_year_dropdown";
import MonthYearDropdownOptions from "../month_year_dropdown_options";
import { safeQuerySelector, safeQuerySelectorAll } from "./test_utils";
type MonthYearDropdownProps = React.ComponentProps<typeof MonthYearDropdown>;
describe("MonthYearDropdown", () => {
let monthYearDropdown: HTMLElement;
let handleChangeResult: Date | null = null;
const mockHandleChange = function (changeInput: Date) {
handleChangeResult = changeInput;
};
function getMonthYearDropdown(
overrideProps: Partial<
Pick<
MonthYearDropdownProps,
| "dropdownMode"
| "date"
| "dateFormat"
| "minDate"
| "maxDate"
| "onChange"
>
> &
Omit<
MonthYearDropdownProps,
| "dropdownMode"
| "date"
| "dateFormat"
| "minDate"
| "maxDate"
| "onChange"
>,
) {
const dateFormatCalendar = "LLLL yyyy";
const date = newDate("2018-01");
const minDate = newDate("2017-07-01");
const maxDate = newDate("2018-06-30");
return render(
<MonthYearDropdown
dropdownMode="scroll"
date={date}
dateFormat={dateFormatCalendar}
minDate={minDate}
maxDate={maxDate}
onChange={mockHandleChange}
{...overrideProps}
/>,
).container;
}
beforeEach(() => {
handleChangeResult = null;
});
describe("scroll mode", () => {
let selectedDate: Date;
beforeEach(() => {
selectedDate = newDate("2018-01");
monthYearDropdown = getMonthYearDropdown({ date: selectedDate });
});
it("shows the selected month year in the initial view", () => {
const selected_month_year_name = formatDate(selectedDate, "LLLL yyyy");
expect(monthYearDropdown?.textContent).toContain(
selected_month_year_name,
);
});
it("opens a list when read view is clicked", () => {
const monthYearReadView = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-read-view",
);
fireEvent.click(monthYearReadView);
const optionsView = monthYearDropdown?.querySelector(
".react-datepicker__month-year-dropdown",
);
expect(optionsView).not.toBeNull();
});
it("closes the dropdown when a month year is clicked", () => {
const monthYearReadView = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-read-view",
);
fireEvent.click(monthYearReadView);
const monthYearOptions = safeQuerySelectorAll(
monthYearDropdown,
".react-datepicker__month-year-option",
);
const monthYearOption = monthYearOptions[0]!;
fireEvent.click(monthYearOption);
expect(
monthYearDropdown?.querySelectorAll(
".react-datepicker__month-year-dropdown",
),
).toHaveLength(0);
});
it("closes the dropdown if outside is clicked", () => {
const date = newDate();
const dateFormatCalendar = "LLLL yyyy";
const onCancelSpy = jest.fn();
render(
<MonthYearDropdownOptions
onCancel={onCancelSpy}
onChange={jest.fn()}
dateFormat={dateFormatCalendar}
date={date}
minDate={subMonths(date, 6)}
maxDate={addMonths(date, 6)}
/>,
);
fireEvent.mouseDown(document.body);
fireEvent.touchStart(document.body);
expect(onCancelSpy).toHaveBeenCalledTimes(1);
});
it("does not call the supplied onChange function when the same month year is clicked", () => {
const monthYearReadView = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-read-view",
);
fireEvent.click(monthYearReadView);
const selectedMonthYear = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-option--selected_month-year",
);
fireEvent.click(selectedMonthYear);
expect(handleChangeResult).toBeNull();
});
it("adds aria-selected to selected option", () => {
const monthYearReadView = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-read-view",
);
fireEvent.click(monthYearReadView);
const ariaSelected = monthYearDropdown
?.querySelector(
".react-datepicker__month-year-option--selected_month-year",
)
?.getAttribute("aria-selected");
expect(ariaSelected).toBe("true");
});
it("does not add aria-selected to non-selected option", () => {
const monthYearReadView = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-read-view",
);
fireEvent.click(monthYearReadView);
const ariaSelected = monthYearDropdown
?.querySelector(".react-datepicker__month-year-option")
?.getAttribute("aria-selected");
expect(ariaSelected).toBeNull();
});
it("calls the supplied onChange function when a different month year is clicked", () => {
const expected_date = newDate("2017-12");
const monthYearReadView = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-read-view",
);
fireEvent.click(monthYearReadView);
const minRequiredMonthYearOptionsLen = 6;
const monthYearOptions = safeQuerySelectorAll(
monthYearDropdown,
".react-datepicker__month-year-option",
minRequiredMonthYearOptionsLen,
);
const monthYearOption = monthYearOptions[5]!;
fireEvent.click(monthYearOption);
expect(handleChangeResult?.toString()).toBe(expected_date.toString());
});
it("should use dateFormat to display date in dropdown", () => {
registerLocale("fi", fi);
let dropdownDateFormat = getMonthYearDropdown({
dateFormat: "LLLL yyyy",
});
expect(dropdownDateFormat.textContent).toBe("January 2018");
dropdownDateFormat = getMonthYearDropdown({ locale: "fi" });
expect(dropdownDateFormat.textContent).toBe("tammikuu 2018");
dropdownDateFormat = getMonthYearDropdown({
locale: "fi",
});
expect(dropdownDateFormat.textContent).toBe("tammikuu 2018");
dropdownDateFormat = getMonthYearDropdown({
dateFormat: "yyyy LLL",
locale: "fi",
});
expect(dropdownDateFormat.textContent).toBe("2018 tammi");
dropdownDateFormat = getMonthYearDropdown({
dateFormat: "yyyy LLL",
locale: "fi",
});
expect(dropdownDateFormat.textContent).toBe("2018 tammi");
});
});
describe("select mode", () => {
it("renders a select", () => {
const expected_date = newDate("2018-01");
let currentMonth = newDate("2017-07");
const maxMonth = newDate("2018-06");
const expected_values: string[] = [];
while (!isAfter(currentMonth, maxMonth)) {
expected_values.push(`${currentMonth.valueOf()}`);
currentMonth = addMonths(currentMonth, 1);
}
monthYearDropdown = getMonthYearDropdown({ dropdownMode: "select" });
const select = monthYearDropdown.querySelector<HTMLSelectElement>(
".react-datepicker__month-year-select",
);
expect(select).not.toBeNull();
expect(select?.value).toBe(`${expected_date.valueOf()}`);
const options = select?.querySelectorAll("option");
expect(Array.from(options ?? []).map((o) => o.value)).toEqual(
expected_values,
);
});
it("renders month options with default locale", () => {
monthYearDropdown = getMonthYearDropdown({ dropdownMode: "select" });
const options = monthYearDropdown.querySelectorAll("option");
expect(Array.from(options).map((o) => o.textContent)).toEqual([
"July 2017",
"August 2017",
"September 2017",
"October 2017",
"November 2017",
"December 2017",
"January 2018",
"February 2018",
"March 2018",
"April 2018",
"May 2018",
"June 2018",
]);
});
it("renders month options with specified locale", () => {
registerLocale("fi", fi);
monthYearDropdown = getMonthYearDropdown({
dropdownMode: "select",
locale: "fi",
});
const options = monthYearDropdown.querySelectorAll("option");
expect(Array.from(options).map((o) => o.textContent)).toEqual([
"heinäkuu 2017",
"elokuu 2017",
"syyskuu 2017",
"lokakuu 2017",
"marraskuu 2017",
"joulukuu 2017",
"tammikuu 2018",
"helmikuu 2018",
"maaliskuu 2018",
"huhtikuu 2018",
"toukokuu 2018",
"kesäkuu 2018",
]);
});
it("does not call the supplied onChange function when the same month is clicked", () => {
const selectedMonth = newDate("2017-11");
monthYearDropdown = getMonthYearDropdown({
dropdownMode: "select",
date: selectedMonth,
});
const select = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-select",
);
fireEvent.change(select, {
target: { value: selectedMonth.valueOf() },
});
expect(handleChangeResult).toBeFalsy();
});
it("calls the supplied onChange function when a different month is clicked", () => {
const selectedMonth = newDate("2017-11");
const monthToClick = newDate("2017-09");
monthYearDropdown = getMonthYearDropdown({
dropdownMode: "select",
date: selectedMonth,
});
const select = safeQuerySelector(
monthYearDropdown,
".react-datepicker__month-year-select",
);
fireEvent.change(select, {
target: { value: monthToClick.valueOf() },
});
expect(handleChangeResult?.valueOf()).toBe(monthToClick.valueOf());
});
});
});
@@ -0,0 +1,61 @@
import { render } from "@testing-library/react";
import React from "react";
import Calendar from "../calendar";
import { formatDate, newDate, subMonths } from "../date_utils";
type CalendarProps = React.ComponentProps<typeof Calendar>;
describe("Multi month calendar", function () {
const dateFormat = "LLLL yyyy";
function getCalendar(
extraProps: Partial<
Pick<
CalendarProps,
"dateFormat" | "onSelect" | "onClickOutside" | "dropdownMode"
>
> &
Omit<
CalendarProps,
| "dateFormat"
| "onSelect"
| "onClickOutside"
| "dropdownMode"
| "showMonthYearDropdown"
>,
) {
return render(
<Calendar
dateFormat={dateFormat}
onSelect={() => {}}
onClickOutside={() => {}}
dropdownMode="scroll"
{...extraProps}
/>,
).container;
}
it("should render multiple months if the months property is present", () => {
const calendar = getCalendar({ monthsShown: 2 });
const months = calendar.querySelectorAll(".react-datepicker__month");
expect(months).toHaveLength(2);
});
it("should render dropdown only on first month", () => {
const calendar = getCalendar({ monthsShown: 2, showYearDropdown: true });
const datepickers = calendar.querySelectorAll(
".react-datepicker__year-dropdown-container",
);
expect(datepickers).toHaveLength(1);
});
it("should render previous months", () => {
const calendar = getCalendar({ monthsShown: 2, showPreviousMonths: true });
const monthDate = calendar.querySelector(
".react-datepicker__current-month",
)?.textContent;
const previousMonth = subMonths(newDate(), 1);
expect(monthDate).toBe(formatDate(previousMonth, "LLLL yyyy"));
});
});
@@ -0,0 +1,137 @@
import { render } from "@testing-library/react";
import React from "react";
import DatePicker from "../";
import type { DatePickerProps } from "../index";
// see https://github.com/microsoft/TypeScript/issues/31501
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type OmitUnion<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
describe("Multiple Dates Selected", function () {
function getDatePicker(
extraProps: Partial<
Pick<
DatePickerProps,
"shouldCloseOnSelect" | "disabledKeyboardNavigation" | "onSelect"
>
> &
OmitUnion<
DatePickerProps,
| "selectsMultiple"
| "onChange"
| "shouldCloseOnSelect"
| "disabledKeyboardNavigation"
| "onSelect"
| "selectsRange"
> & {
selectsMultiple?: true;
},
) {
return render(
<DatePicker
selectsMultiple
onChange={() => {}}
shouldCloseOnSelect={false}
disabledKeyboardNavigation
onSelect={() => {}}
{...extraProps}
/>,
);
}
it("should handle text format for no selected date", () => {
const { container: datePicker } = getDatePicker({
selectsMultiple: true,
selectedDates: [],
});
const input = datePicker.querySelector("input");
expect(input).not.toBeNull();
expect(input?.value).toBe("");
});
it("should handle text format for one selected date", () => {
const { container: datePicker } = getDatePicker({
selectsMultiple: true,
selectedDates: [new Date("2024/01/01")],
});
const input = datePicker.querySelector("input");
expect(input).not.toBeNull();
expect(input?.value).toBe("01/01/2024");
});
it("should handle text format for two selected dates", () => {
const { container: datePicker } = getDatePicker({
selectsMultiple: true,
selectedDates: [new Date("2024/01/01"), new Date("2024/01/15")],
});
const input = datePicker.querySelector("input");
expect(input).not.toBeNull();
expect(input?.value).toBe("01/01/2024, 01/15/2024");
});
it("should handle text format for more than two selected dates", () => {
const { container: datePicker } = getDatePicker({
selectsMultiple: true,
selectedDates: [
new Date("2024/01/01"),
new Date("2024/01/15"),
new Date("2024/03/15"),
],
});
const input = datePicker.querySelector("input");
expect(input).not.toBeNull();
expect(input?.value).toBe("01/01/2024 (+2)");
});
it("should override default format when formatMultipleDates is provided", () => {
const { container: datePicker } = getDatePicker({
selectsMultiple: true,
selectedDates: [
new Date("2024/01/01"),
new Date("2024/01/15"),
new Date("2024/03/15"),
],
formatMultipleDates: (dates, formatDate) =>
dates.map(formatDate).join(" | "),
});
const input = datePicker.querySelector("input");
expect(input).not.toBeNull();
expect(input?.value).toBe("01/01/2024 | 01/15/2024 | 03/15/2024");
});
it("should pass correct arguments to formatMultipleDates", () => {
const selectedDates = [new Date("2024/01/01"), new Date("2024/01/15")];
const mockFormatter = jest.fn(
(dates: Date[], formatDate: (d: Date) => string) =>
dates.map(formatDate).join(", "),
);
getDatePicker({
selectsMultiple: true,
selectedDates,
formatMultipleDates: mockFormatter,
});
expect(mockFormatter).toHaveBeenCalledTimes(1);
const [receivedDates, receivedFormatDate] = mockFormatter.mock.calls[0]!;
expect(receivedDates).toHaveLength(2);
expect(receivedDates[0]?.getTime()).toBe(selectedDates[0]?.getTime());
expect(receivedDates[1]?.getTime()).toBe(selectedDates[1]?.getTime());
expect(typeof receivedFormatDate).toBe("function");
expect(receivedFormatDate(new Date("2024/01/01"))).toBe("01/01/2024");
});
});
@@ -0,0 +1,364 @@
import { render, fireEvent } from "@testing-library/react";
import React from "react";
import { PopperComponent } from "../popper_component";
// Mock the withFloating HOC
jest.mock("../with_floating", () => ({
__esModule: true,
default: <T,>(Component: React.ComponentType<T>) => Component,
}));
// Mock FloatingArrow component
jest.mock("@floating-ui/react", () => ({
FloatingArrow: ({ className }: { className: string }) => (
<div data-testid="floating-arrow" className={className} />
),
}));
describe("PopperComponent", () => {
const mockPopperProps = {
refs: {
reference: { current: null },
floating: { current: null },
setFloating: jest.fn(),
setReference: jest.fn(),
setPositionReference: jest.fn(),
},
floatingStyles: { position: "absolute" as const, top: 0, left: 0 },
placement: "bottom" as const,
strategy: "absolute" as const,
x: 0,
y: 0,
middlewareData: {},
isPositioned: true,
update: jest.fn(),
elements: {
reference: null,
floating: null,
domReference: null,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: {} as any,
arrowRef: { current: null },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
const defaultProps = {
popperComponent: <div data-testid="popper-content">Popper Content</div>,
targetComponent: <div data-testid="target">Target</div>,
popperOnKeyDown: jest.fn(),
popperProps: mockPopperProps,
};
beforeEach(() => {
jest.clearAllMocks();
});
it("renders target component", () => {
const { container } = render(<PopperComponent {...defaultProps} />);
expect(container.querySelector('[data-testid="target"]')).toBeTruthy();
});
it("renders target component with wrapper class", () => {
const { container } = render(<PopperComponent {...defaultProps} />);
const wrapper = container.querySelector(".react-datepicker-wrapper");
expect(wrapper).toBeTruthy();
expect(wrapper?.querySelector('[data-testid="target"]')).toBeTruthy();
});
it("applies custom wrapperClassName", () => {
const { container } = render(
<PopperComponent {...defaultProps} wrapperClassName="custom-wrapper" />,
);
const wrapper = container.querySelector(
".react-datepicker-wrapper.custom-wrapper",
);
expect(wrapper).toBeTruthy();
});
it("hides popper when hidePopper is true", () => {
const { container } = render(
<PopperComponent {...defaultProps} hidePopper={true} />,
);
expect(container.querySelector('[data-testid="popper-content"]')).toBe(
null,
);
});
it("shows popper when hidePopper is false", () => {
const { container } = render(
<PopperComponent {...defaultProps} hidePopper={false} />,
);
expect(
container.querySelector('[data-testid="popper-content"]'),
).toBeTruthy();
});
it("applies popper className", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
className="custom-popper"
/>,
);
const popper = container.querySelector(
".react-datepicker-popper.custom-popper",
);
expect(popper).toBeTruthy();
});
it("applies data-placement attribute", () => {
const { container } = render(
<PopperComponent {...defaultProps} hidePopper={false} />,
);
const popper = container.querySelector(".react-datepicker-popper");
expect(popper?.getAttribute("data-placement")).toBe("bottom");
});
it("calls popperOnKeyDown when key is pressed in popper", () => {
const onKeyDownMock = jest.fn();
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
popperOnKeyDown={onKeyDownMock}
/>,
);
const popper = container.querySelector(
".react-datepicker-popper",
) as HTMLElement;
fireEvent.keyDown(popper, { key: "Escape" });
expect(onKeyDownMock).toHaveBeenCalledTimes(1);
});
it("renders arrow when showArrow is true", () => {
const { container } = render(
<PopperComponent {...defaultProps} hidePopper={false} showArrow={true} />,
);
expect(
container.querySelector('[data-testid="floating-arrow"]'),
).toBeTruthy();
});
it("does not render arrow when showArrow is false", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
showArrow={false}
/>,
);
expect(container.querySelector('[data-testid="floating-arrow"]')).toBe(
null,
);
});
it("wraps popper in TabLoop when enableTabLoop is true", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
enableTabLoop={true}
/>,
);
expect(container.querySelector(".react-datepicker__tab-loop")).toBeTruthy();
});
it("renders in portal when portalId is provided", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
portalId="test-portal"
/>,
);
// Popper should not be in the main container
expect(container.querySelector('[data-testid="popper-content"]')).toBe(
null,
);
// Popper should be in the portal
const portalRoot = document.getElementById("test-portal");
expect(portalRoot).toBeTruthy();
expect(
portalRoot?.querySelector('[data-testid="popper-content"]'),
).toBeTruthy();
// Cleanup
portalRoot?.remove();
});
it("does not render in portal when hidePopper is true even with portalId", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={true}
portalId="test-portal-2"
/>,
);
expect(container.querySelector('[data-testid="popper-content"]')).toBe(
null,
);
expect(document.getElementById("test-portal-2")).toBe(null);
});
it("wraps popper in custom container when popperContainer is provided", () => {
const CustomContainer: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => <div data-testid="custom-container">{children}</div>;
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
popperContainer={CustomContainer}
/>,
);
expect(
container.querySelector('[data-testid="custom-container"]'),
).toBeTruthy();
expect(
container
.querySelector('[data-testid="custom-container"]')
?.querySelector('[data-testid="popper-content"]'),
).toBeTruthy();
});
it("applies floating styles to popper", () => {
const customStyles = {
position: "absolute" as const,
top: 100,
left: 200,
};
const customPopperProps = {
...mockPopperProps,
floatingStyles: customStyles,
};
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
popperProps={customPopperProps}
/>,
);
const popper = container.querySelector(
".react-datepicker-popper",
) as HTMLElement;
expect(popper.style.position).toBe("absolute");
expect(popper.style.top).toBe("100px");
expect(popper.style.left).toBe("200px");
});
it("renders with shadow DOM when portalHost is provided", () => {
const shadowHost = document.createElement("div");
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({ mode: "open" });
render(
<PopperComponent
{...defaultProps}
hidePopper={false}
portalId="shadow-portal"
portalHost={shadowRoot}
/>,
);
const portalRoot = shadowRoot.getElementById("shadow-portal");
expect(portalRoot).toBeTruthy();
expect(
portalRoot?.querySelector('[data-testid="popper-content"]'),
).toBeTruthy();
shadowHost.remove();
});
describe("monthHeaderPosition", () => {
it("should not add header position classes by default", () => {
const { container } = render(
<PopperComponent {...defaultProps} hidePopper={false} />,
);
const popper = container.querySelector(".react-datepicker-popper");
expect(
popper?.classList.contains("react-datepicker-popper--header-middle"),
).toBe(false);
expect(
popper?.classList.contains("react-datepicker-popper--header-bottom"),
).toBe(false);
});
it("should add header-middle class when monthHeaderPosition is 'middle'", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
monthHeaderPosition="middle"
/>,
);
const popper = container.querySelector(".react-datepicker-popper");
expect(
popper?.classList.contains("react-datepicker-popper--header-middle"),
).toBe(true);
expect(
popper?.classList.contains("react-datepicker-popper--header-bottom"),
).toBe(false);
});
it("should add header-bottom class when monthHeaderPosition is 'bottom'", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
monthHeaderPosition="bottom"
/>,
);
const popper = container.querySelector(".react-datepicker-popper");
expect(
popper?.classList.contains("react-datepicker-popper--header-bottom"),
).toBe(true);
expect(
popper?.classList.contains("react-datepicker-popper--header-middle"),
).toBe(false);
});
it("should not add header position classes when monthHeaderPosition is 'top'", () => {
const { container } = render(
<PopperComponent
{...defaultProps}
hidePopper={false}
monthHeaderPosition="top"
/>,
);
const popper = container.querySelector(".react-datepicker-popper");
expect(
popper?.classList.contains("react-datepicker-popper--header-middle"),
).toBe(false);
expect(
popper?.classList.contains("react-datepicker-popper--header-bottom"),
).toBe(false);
});
});
});
+193
View File
@@ -0,0 +1,193 @@
import { cleanup, render } from "@testing-library/react";
import React from "react";
import Portal from "../portal";
describe("Portal", () => {
afterEach(() => {
const portals = document.querySelectorAll('[id^="test-portal"]');
portals.forEach((portal) => portal.remove());
cleanup();
});
it("renders children into a portal", () => {
const { container } = render(
<Portal portalId="test-portal-1">
<div data-testid="portal-content">Portal Content</div>
</Portal>,
);
expect(
container.querySelector('[data-testid="portal-content"]'),
).toBeNull();
const portalRoot = document.getElementById("test-portal-1");
expect(portalRoot).toBeTruthy();
expect(
portalRoot?.querySelector('[data-testid="portal-content"]'),
).toBeTruthy();
});
it("creates portal root if it doesn't exist", () => {
expect(document.getElementById("test-portal-2")).toBeNull();
render(
<Portal portalId="test-portal-2">
<div>Content</div>
</Portal>,
);
const portalRoot = document.getElementById("test-portal-2");
expect(portalRoot).not.toBeNull();
expect(portalRoot?.parentElement).toBe(document.body);
});
it("uses existing portal root if it exists", () => {
const existingRoot = document.createElement("div");
existingRoot.id = "test-portal-3";
document.body.appendChild(existingRoot);
render(
<Portal portalId="test-portal-3">
<div data-testid="existing-content">Using Existing</div>
</Portal>,
);
const portalRoot = document.getElementById("test-portal-3");
expect(portalRoot).toBe(existingRoot);
expect(
portalRoot?.querySelector('[data-testid="existing-content"]'),
).toBeTruthy();
existingRoot.remove();
});
it("removes portal content on unmount", () => {
const { unmount } = render(
<Portal portalId="test-portal-4">
<div data-testid="portal-content">Cleanup Test</div>
</Portal>,
);
const portalRoot = document.getElementById("test-portal-4");
expect(
portalRoot?.querySelector('[data-testid="portal-content"]'),
).toBeTruthy();
unmount();
const stillExists = document.getElementById("test-portal-4");
expect(stillExists).toBeTruthy();
expect(
stillExists?.querySelector('[data-testid="portal-content"]'),
).toBeNull();
});
it("renders multiple children correctly", () => {
render(
<Portal portalId="test-portal-5">
<div data-testid="child-1">Child 1</div>
<div data-testid="child-2">Child 2</div>
<span data-testid="child-3">Child 3</span>
</Portal>,
);
const portalRoot = document.getElementById("test-portal-5");
expect(portalRoot?.querySelector('[data-testid="child-1"]')).toBeTruthy();
expect(portalRoot?.querySelector('[data-testid="child-2"]')).toBeTruthy();
expect(portalRoot?.querySelector('[data-testid="child-3"]')).toBeTruthy();
});
it("handles multiple portals", () => {
render(
<Portal portalId="test-portal-6a">
<div>Portal A</div>
</Portal>,
);
render(
<Portal portalId="test-portal-6b">
<div>Portal B</div>
</Portal>,
);
expect(document.getElementById("test-portal-6a")).not.toBeNull();
expect(document.getElementById("test-portal-6b")).not.toBeNull();
});
it("works with shadow DOM when portalHost is provided", () => {
const shadowHost = document.createElement("div");
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({ mode: "open" });
render(
<Portal portalId="test-portal-shadow" portalHost={shadowRoot}>
<div data-testid="shadow-content">Shadow Content</div>
</Portal>,
);
const portalRoot = shadowRoot.getElementById("test-portal-shadow");
expect(portalRoot).toBeTruthy();
expect(
portalRoot?.querySelector('[data-testid="shadow-content"]'),
).toBeTruthy();
shadowHost.remove();
});
it("appends to portalHost instead of document.body when provided", () => {
const customHost = document.createElement("div");
document.body.appendChild(customHost);
const shadowRoot = customHost.attachShadow({ mode: "open" });
render(
<Portal portalId="test-portal-custom-host" portalHost={shadowRoot}>
<div data-testid="custom-host-content">Custom Host Content</div>
</Portal>,
);
const portalRoot = shadowRoot.getElementById("test-portal-custom-host");
expect(portalRoot).toBeTruthy();
expect(portalRoot?.parentNode).toBe(shadowRoot);
customHost.remove();
});
it("creates portal root in portalHost when it doesn't exist", () => {
const shadowHost = document.createElement("div");
document.body.appendChild(shadowHost);
const shadowRoot = shadowHost.attachShadow({ mode: "open" });
render(
<Portal portalId="test-portal-7" portalHost={shadowRoot}>
<div>Shadow Portal</div>
</Portal>,
);
const portalRoot = shadowRoot.getElementById("test-portal-7");
expect(portalRoot).not.toBeNull();
expect(shadowRoot.contains(portalRoot!)).toBe(true);
shadowHost.remove();
});
it("handles re-renders correctly", () => {
const { rerender } = render(
<Portal portalId="test-portal-8">
<div data-testid="content-1">Content 1</div>
</Portal>,
);
const portalRoot = document.getElementById("test-portal-8");
expect(portalRoot?.querySelector('[data-testid="content-1"]')).toBeTruthy();
rerender(
<Portal portalId="test-portal-8">
<div data-testid="content-2">Content 2</div>
</Portal>,
);
expect(portalRoot?.querySelector('[data-testid="content-1"]')).toBeNull();
expect(portalRoot?.querySelector('[data-testid="content-2"]')).toBeTruthy();
});
});
@@ -0,0 +1,161 @@
import { render } from "@testing-library/react";
import React from "react";
import DatePicker from "../index";
import { ReactDatePickerCustomDayNameProps } from "../calendar";
describe("renderCustomDayName", () => {
it("should call renderCustomDayName function with correct parameters", () => {
const renderCustomDayName = jest.fn(
({ shortName }: ReactDatePickerCustomDayNameProps) => (
<span>{shortName}</span>
),
);
render(<DatePicker renderCustomDayName={renderCustomDayName} inline />);
// Should be called 7 times (one for each day of the week)
expect(renderCustomDayName).toHaveBeenCalledTimes(7);
// Check that it's called with correct parameters
const firstCall = renderCustomDayName.mock.calls[0]?.[0];
expect(firstCall).toBeDefined();
expect(firstCall).toHaveProperty("day");
expect(firstCall).toHaveProperty("shortName");
expect(firstCall).toHaveProperty("fullName");
expect(firstCall).toHaveProperty("locale");
expect(firstCall).toHaveProperty("customDayNameCount");
expect(firstCall?.day).toBeInstanceOf(Date);
expect(typeof firstCall?.shortName).toBe("string");
expect(typeof firstCall?.fullName).toBe("string");
expect(typeof firstCall?.customDayNameCount).toBe("number");
});
it("should render custom day names", () => {
const renderCustomDayName = ({
shortName,
}: ReactDatePickerCustomDayNameProps) => (
<span className="custom-day-name">Custom-{shortName}</span>
);
const { container } = render(
<DatePicker renderCustomDayName={renderCustomDayName} inline />,
);
const customDayNames = container.querySelectorAll(".custom-day-name");
expect(customDayNames).toHaveLength(7);
expect(customDayNames[0]?.textContent).toContain("Custom-");
});
it("should render default day names when renderCustomDayName is not provided", () => {
const { container } = render(<DatePicker inline />);
const dayNames = container.querySelectorAll(".react-datepicker__day-name");
expect(dayNames).toHaveLength(7);
// Check that default structure is present (sr-only + aria-hidden)
const firstDayName = dayNames[0];
expect(
firstDayName?.querySelector(".react-datepicker__sr-only"),
).not.toBeNull();
expect(firstDayName?.querySelector('[aria-hidden="true"]')).not.toBeNull();
});
it("should use custom day names with accessibility", () => {
const renderCustomDayName = ({
shortName,
fullName,
}: ReactDatePickerCustomDayNameProps) => (
<>
<span className="react-datepicker__sr-only">{fullName}</span>
<span aria-hidden="true">{shortName}</span>
</>
);
const { container } = render(
<DatePicker renderCustomDayName={renderCustomDayName} inline />,
);
const dayNames = container.querySelectorAll(".react-datepicker__day-name");
expect(dayNames).toHaveLength(7);
// Check that accessibility structure is maintained
dayNames.forEach((dayName) => {
expect(
dayName.querySelector(".react-datepicker__sr-only"),
).not.toBeNull();
expect(dayName.querySelector('[aria-hidden="true"]')).not.toBeNull();
});
});
it("should apply weekDayClassName along with custom day names", () => {
const weekDayClassName = (date: Date) => {
return date.getDay() === 0 || date.getDay() === 6 ? "weekend" : "";
};
const renderCustomDayName = ({
shortName,
}: ReactDatePickerCustomDayNameProps) => <span>{shortName}</span>;
const { container } = render(
<DatePicker
renderCustomDayName={renderCustomDayName}
weekDayClassName={weekDayClassName}
inline
/>,
);
const weekendDays = container.querySelectorAll(
".react-datepicker__day-name.weekend",
);
// Should have 2 weekend days (Saturday and Sunday)
expect(weekendDays.length).toBeGreaterThanOrEqual(2);
});
it("should pass locale to renderCustomDayName", () => {
const renderCustomDayName = jest.fn(
({ shortName }: ReactDatePickerCustomDayNameProps) => (
<span>{shortName}</span>
),
);
render(
<DatePicker
renderCustomDayName={renderCustomDayName}
locale="en-US"
inline
/>,
);
const firstCall = renderCustomDayName.mock.calls[0]?.[0];
expect(firstCall?.locale).toBe("en-US");
});
it("should pass customDayNameCount when displaying multiple months", () => {
const renderCustomDayName = jest.fn(
({ shortName }: ReactDatePickerCustomDayNameProps) => (
<span>{shortName}</span>
),
);
render(
<DatePicker
renderCustomDayName={renderCustomDayName}
monthsShown={3}
inline
/>,
);
// Should be called 7 times per month, so 21 times for 3 months
expect(renderCustomDayName).toHaveBeenCalledTimes(21);
// Check that customDayNameCount is different for each month
const firstMonthCall = renderCustomDayName.mock.calls[0]?.[0];
const secondMonthCall = renderCustomDayName.mock.calls[7]?.[0];
const thirdMonthCall = renderCustomDayName.mock.calls[14]?.[0];
expect(firstMonthCall?.customDayNameCount).toBe(0);
expect(secondMonthCall?.customDayNameCount).toBe(1);
expect(thirdMonthCall?.customDayNameCount).toBe(2);
});
});
+14
View File
@@ -0,0 +1,14 @@
import axe from "axe-core";
const wrapper = document.createElement("main");
document.body.appendChild(wrapper);
export function runAxe(domNode: Node): Promise<void> {
wrapper.appendChild(domNode);
return axe
.run(domNode)
.then(({ violations }) => {
expect(violations).toHaveLength(0);
})
.finally(() => wrapper.removeChild(domNode));
}
+90
View File
@@ -0,0 +1,90 @@
import { render } from "@testing-library/react";
import React from "react";
import ShadowRoot from "./helper_components/shadow_root";
describe("ShadowRoot", () => {
it("should render children in shadow root", () => {
const { container } = render(
<ShadowRoot>
<div className="test-child">Test Content</div>
</ShadowRoot>,
);
const hostElement = container.querySelector("div");
expect(hostElement).not.toBeNull();
expect(hostElement?.shadowRoot).not.toBeNull();
// Content should be in shadow root
const childInShadow = hostElement?.shadowRoot?.querySelector(".test-child");
expect(childInShadow).not.toBeNull();
});
it("should handle multiple children", () => {
const { container } = render(
<ShadowRoot>
<div className="child-1">Child 1</div>
<div className="child-2">Child 2</div>
</ShadowRoot>,
);
const hostElement = container.querySelector("div");
const shadowRoot = hostElement?.shadowRoot;
expect(shadowRoot?.querySelector(".child-1")).not.toBeNull();
expect(shadowRoot?.querySelector(".child-2")).not.toBeNull();
});
it("should initialize shadow root only once", () => {
const { rerender } = render(
<ShadowRoot>
<div>Initial</div>
</ShadowRoot>,
);
// Rerender to test the early return when already initialized (line 19)
rerender(
<ShadowRoot>
<div>Updated</div>
</ShadowRoot>,
);
// Should still work after rerender
expect(true).toBe(true);
});
it("should handle null/undefined children gracefully", () => {
const { container } = render(<ShadowRoot>{null}</ShadowRoot>);
const hostElement = container.querySelector("div");
expect(hostElement).not.toBeNull();
expect(hostElement?.shadowRoot).not.toBeNull();
});
it("should use existing shadow root if already attached", () => {
const div = document.createElement("div");
const existingShadowRoot = div.attachShadow({ mode: "open" });
existingShadowRoot.innerHTML = "<span>Existing</span>";
// This tests line 23: container.shadowRoot ?? container.attachShadow
const { container } = render(
<ShadowRoot>
<div>New Content</div>
</ShadowRoot>,
);
expect(container.querySelector("div")).not.toBeNull();
});
it("should avoid re-initializing when effect runs multiple times", () => {
const { container } = render(
<React.StrictMode>
<ShadowRoot>
<div>Strict Content</div>
</ShadowRoot>
</React.StrictMode>,
);
expect(container.querySelector("div")).not.toBeNull();
});
});
+293
View File
@@ -0,0 +1,293 @@
import { fireEvent, render } from "@testing-library/react";
import { act } from "react";
import React from "react";
import DatePicker from "../index";
import TimeComponent from "../time";
import { safeQuerySelector, setupMockResizeObserver } from "./test_utils";
describe("DatePicker", () => {
beforeAll(() => {
setupMockResizeObserver();
});
it("should show time component when showTimeSelect prop is present", () => {
const { container } = render(<DatePicker showTimeSelect open />);
const timeComponent = container.querySelector(
".react-datepicker__time-container",
);
expect(timeComponent).not.toBeNull();
});
it("should have custom time caption", () => {
const { container } = render(<TimeComponent timeCaption="Custom time" />);
const caption = container.querySelector(".react-datepicker-time__header");
expect(caption?.textContent).toEqual("Custom time");
});
describe("Time Select Only", () => {
let datePicker: HTMLElement;
beforeEach(() => {
datePicker = render(
<DatePicker showTimeSelect showTimeSelectOnly todayButton="Today" />,
).container;
const input = safeQuerySelector(datePicker, "input");
fireEvent.click(input);
});
it("should not show month container when showTimeSelectOnly prop is present", () => {
const elem = datePicker.querySelectorAll(
".react-datepicker__month-container",
);
expect(elem).toHaveLength(0);
});
it("should not show previous month button when showTimeSelectOnly prop is present", () => {
const elem = datePicker.querySelectorAll(
".react-datepicker__navigation--previous",
);
expect(elem).toHaveLength(0);
});
it("should not show next month button when showTimeSelectOnly prop is present", () => {
const elem = datePicker.querySelectorAll(
".react-datepicker__navigation--next",
);
expect(elem).toHaveLength(0);
});
it("should not show today button when showTimeSelectOnly prop is present", () => {
const elem = datePicker.querySelectorAll(
".react-datepicker__today-button",
);
expect(elem).toHaveLength(0);
});
});
describe("Time input interactions", () => {
it("should show input-time container when showTimeInput prop is present", () => {
const { container } = render(<DatePicker showTimeInput open />);
const component = container.querySelector(
".react-datepicker__input-time-container",
);
expect(component).not.toBeNull();
});
it("should retain focus on input after value change", () => {
const { container } = render(<DatePicker showTimeInput open />);
const input = safeQuerySelector<HTMLInputElement>(container, "input");
act(() => {
input.focus();
});
expect(document.activeElement).toBe(input);
fireEvent.change(input, {
target: { value: "13:00" },
});
expect(document.activeElement).toBe(input);
});
it("should focus the time input when clicked", () => {
const { container } = render(
<DatePicker shouldCloseOnSelect={false} showTimeInput />,
);
const input = safeQuerySelector(container, "input");
fireEvent.focus(input);
const timeInput = safeQuerySelector<HTMLInputElement>(
container,
'input[type="time"].react-datepicker-time__input',
);
fireEvent.click(timeInput);
expect(document.activeElement).toBe(timeInput);
});
it("should handle invalid time input gracefully", () => {
const onChange = jest.fn();
const { container } = render(
<DatePicker
selected={new Date("2024-01-15T10:00:00")}
onChange={onChange}
showTimeInput
/>,
);
const input = safeQuerySelector(container, "input");
fireEvent.focus(input);
const timeInput = safeQuerySelector<HTMLInputElement>(
container,
'input[type="time"].react-datepicker-time__input',
);
fireEvent.change(timeInput, {
target: { value: "invalid" },
});
expect(onChange).toHaveBeenCalled();
});
it("should handle time change when no date is selected", () => {
const onChange = jest.fn();
const { container } = render(
<DatePicker selected={null} onChange={onChange} showTimeInput />,
);
const input = safeQuerySelector(container, "input");
fireEvent.focus(input);
const timeInput = safeQuerySelector<HTMLInputElement>(
container,
'input[type="time"].react-datepicker-time__input',
);
fireEvent.change(timeInput, {
target: { value: "14:30" },
});
expect(onChange).toHaveBeenCalled();
});
it("should call onChange with updated date when valid time is entered", () => {
const onChange = jest.fn();
const selectedDate = new Date("2024-01-15T10:00:00");
const { container } = render(
<DatePicker
selected={selectedDate}
onChange={onChange}
showTimeInput
/>,
);
const input = safeQuerySelector(container, "input");
fireEvent.focus(input);
const timeInput = safeQuerySelector<HTMLInputElement>(
container,
'input[type="time"].react-datepicker-time__input',
);
fireEvent.change(timeInput, {
target: { value: "15:45" },
});
const expectedDate = new Date(selectedDate);
expectedDate.setHours(15);
expectedDate.setMinutes(45);
expect(onChange).toHaveBeenCalledWith(expectedDate);
});
});
describe("Time Select Only with openToDate", () => {
it("should use openToDate as the base date when selecting time with showTimeSelectOnly and selected is null", () => {
const onChange = jest.fn();
const openToDate = new Date("2025-11-01T00:00:00");
const { container } = render(
<DatePicker
selected={null}
openToDate={openToDate}
onChange={onChange}
showTimeSelect
showTimeSelectOnly
/>,
);
const input = safeQuerySelector(container, "input");
fireEvent.click(input);
// Find and click a time option (e.g., 09:00)
const timeListItems = container.querySelectorAll(
".react-datepicker__time-list-item",
);
expect(timeListItems.length).toBeGreaterThan(0);
// Click on a time option
fireEvent.click(timeListItems[0]!);
expect(onChange).toHaveBeenCalled();
const selectedDate = onChange.mock.calls[0][0] as Date;
// Verify the date part comes from openToDate
expect(selectedDate.getFullYear()).toBe(2025);
expect(selectedDate.getMonth()).toBe(10); // November is month 10 (0-indexed)
expect(selectedDate.getDate()).toBe(1);
});
it("should use current date when showTimeSelectOnly is true and neither selected nor openToDate is provided", () => {
const onChange = jest.fn();
const today = new Date();
const { container } = render(
<DatePicker
selected={null}
onChange={onChange}
showTimeSelect
showTimeSelectOnly
/>,
);
const input = safeQuerySelector(container, "input");
fireEvent.click(input);
const timeListItems = container.querySelectorAll(
".react-datepicker__time-list-item",
);
expect(timeListItems.length).toBeGreaterThan(0);
fireEvent.click(timeListItems[0]!);
expect(onChange).toHaveBeenCalled();
const selectedDate = onChange.mock.calls[0][0] as Date;
// Verify the date part comes from today
expect(selectedDate.getFullYear()).toBe(today.getFullYear());
expect(selectedDate.getMonth()).toBe(today.getMonth());
expect(selectedDate.getDate()).toBe(today.getDate());
});
it("should use openToDate for showTimeInput when selected is null", () => {
const onChange = jest.fn();
const openToDate = new Date("2025-11-01T00:00:00");
const { container } = render(
<DatePicker
selected={null}
openToDate={openToDate}
onChange={onChange}
showTimeInput
/>,
);
const input = safeQuerySelector(container, "input");
fireEvent.focus(input);
const timeInput = safeQuerySelector<HTMLInputElement>(
container,
'input[type="time"].react-datepicker-time__input',
);
fireEvent.change(timeInput, {
target: { value: "14:30" },
});
expect(onChange).toHaveBeenCalled();
const selectedDate = onChange.mock.calls[0][0] as Date;
// Verify the date part comes from openToDate
expect(selectedDate.getFullYear()).toBe(2025);
expect(selectedDate.getMonth()).toBe(10); // November
expect(selectedDate.getDate()).toBe(1);
expect(selectedDate.getHours()).toBe(14);
expect(selectedDate.getMinutes()).toBe(30);
});
});
});

Some files were not shown because too many files have changed in this diff Show More