Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add MonthsDropdown and YearsDropdown custom components #2454

Merged
merged 3 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/DayPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,22 @@ test("extends the default locale", () => {
// Check if the custom month name is rendered
expect(grid("bar 2024")).toBeInTheDocument();
});

test("should render the custom components", () => {
render(
<DayPicker
footer="test"
captionLayout="dropdown"
components={{
Nav: () => <div>Custom Nav</div>,
YearsDropdown: () => <div>Custom YearsDropdown</div>,
MonthsDropdown: () => <div>Custom MonthsDropdown</div>,
Footer: () => <div>Custom Footer</div>
}}
/>
);
expect(screen.getByText("Custom Nav")).toBeInTheDocument();
expect(screen.getByText("Custom Footer")).toBeInTheDocument();
expect(screen.getByText("Custom YearsDropdown")).toBeInTheDocument();
expect(screen.getByText("Custom MonthsDropdown")).toBeInTheDocument();
});
4 changes: 2 additions & 2 deletions src/DayPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ export function DayPicker(props: DayPickerProps) {
>
{captionLayout === "dropdown" ||
captionLayout === "dropdown-months" ? (
<components.Dropdown
<components.MonthsDropdown
className={classNames[UI.MonthsDropdown]}
aria-label={labelMonthDropdown()}
classNames={classNames}
Expand All @@ -362,7 +362,7 @@ export function DayPicker(props: DayPickerProps) {
)}
{captionLayout === "dropdown" ||
captionLayout === "dropdown-years" ? (
<components.Dropdown
<components.YearsDropdown
className={classNames[UI.YearsDropdown]}
aria-label={labelYearDropdown(labelOptions)}
classNames={classNames}
Expand Down
8 changes: 8 additions & 0 deletions src/components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ export type DropdownOption = {
*/
export function Dropdown(
props: {
/**
* @deprecated Use{@link useDayPicker} hook to get the list of internal
* components.
*/
components: CustomComponents;
/**
* @deprecated Use {@link useDayPicker} hook to get the list of internal
* class names.
*/
classNames: ClassNames;
options?: DropdownOption[] | undefined;
} & Omit<JSX.IntrinsicElements["select"], "children">
Expand Down
16 changes: 16 additions & 0 deletions src/components/MonthsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

import { useDayPicker } from "../useDayPicker.js";

import { DropdownProps } from "./Dropdown.js";

/**
* Render the dropdown to navigate between months.
*
* @group Components
* @see https://daypicker.dev/guides/custom-components
*/
export function MonthsDropdown(props: DropdownProps) {
const { components } = useDayPicker();
return <components.Dropdown {...props} />;
}
16 changes: 16 additions & 0 deletions src/components/YearsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

import { useDayPicker } from "../useDayPicker.js";

import { DropdownProps } from "./Dropdown.js";

/**
* Render the dropdown to navigate between years.
*
* @group Components
* @see https://daypicker.dev/guides/custom-components
*/
export function YearsDropdown(props: DropdownProps) {
const { components } = useDayPicker();
return <components.Dropdown {...props} />;
}
10 changes: 6 additions & 4 deletions src/components/custom-components.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export * from "./Button.js";
export * from "./PreviousMonthButton.js";
export * from "./NextMonthButton.js";
export * from "./Chevron.js";
export * from "./CaptionLabel.js";
export * from "./Chevron.js";
export * from "./Day.js";
export * from "./DayButton.js";
export * from "./Dropdown.js";
Expand All @@ -12,13 +10,17 @@ export * from "./Month.js";
export * from "./MonthCaption.js";
export * from "./MonthGrid.js";
export * from "./Months.js";
export * from "./MonthsDropdown.js";
export * from "./Nav.js";
export * from "./NextMonthButton.js";
export * from "./Option.js";
export * from "./PreviousMonthButton.js";
export * from "./Root.js";
export * from "./Select.js";
export * from "./Weeks.js";
export * from "./Week.js";
export * from "./Weekday.js";
export * from "./Weekdays.js";
export * from "./WeekNumber.js";
export * from "./WeekNumberHeader.js";
export * from "./Weeks.js";
export * from "./YearsDropdown.js";
4 changes: 4 additions & 0 deletions src/types/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export type CustomComponents = {
WeekNumber: typeof components.WeekNumber;
/** Render the header of the week number column. */
WeekNumberHeader: typeof components.WeekNumberHeader;
/** Render the dropdown with the months. */
MonthsDropdown: typeof components.MonthsDropdown;
/** Render the dropdown with the years. */
YearsDropdown: typeof components.YearsDropdown;
};

/** @private */
Expand Down
87 changes: 52 additions & 35 deletions website/docs/guides/custom-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ When customizing components, familiarize yourself with the [API Reference](../ap

:::note Custom Components vs Formatters

Custom components extend DayPicker more than [formatters](../docs/translation.mdx#custom-formatters), which are used to format the content of the calendar.
Custom components let you extend DayPicker beyond what [formatters](../docs/translation.mdx#custom-formatters) can do. Formatters change the content of the calendar, while custom components can change the structure of the HTML elements..

:::

## Implementing a Custom Component

Pass the components you want to customize to the `components` prop. This prop accepts any of the [`CustomComponents`](../api/type-aliases/CustomComponents.md) types.
Pass the components you want to customize to the `components` prop. See the [list of custom components](#list-of-custom-components) below.

```tsx
<DayPicker
Expand All @@ -40,36 +40,35 @@ Pass the components you want to customize to the `components` prop. This prop ac
/>
```

<details>
<summary>List of Customizable Components</summary>

| Function | Description |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [`CaptionLabel`](../api/functions/CaptionLabel.md) | Render the label in the month caption. |
| [`Chevron`](../api/functions/Chevron.md) | Render the chevron icon used in the navigation buttons and dropdowns. |
| [`Day`](../api/functions/Day.md) | Render the gridcell of a day in the calendar and handle the interaction and the focus with they day. |
| [`DayButton`](../api/functions/DayButton.md) | Render the button for a day in the calendar. |
| [`Dropdown`](../api/functions/Dropdown.md) | Render a dropdown component to use in the navigation bar. |
| [`DropdownNav`](../api/functions/DropdownNav.md) | Render the the navigation dropdowns. |
| [`Footer`](../api/functions/Footer.md) | Component wrapping the footer. |
| [`Month`](../api/functions/Month.md) | Render the grid with the weekday header row and the weeks for the given month. |
| [`MonthCaption`](../api/functions/MonthCaption.md) | Render the caption of a month in the calendar. |
| [`MonthGrid`](../api/functions/MonthGrid.md) | Render the grid of days in a month. |
| [`Months`](../api/functions/Months.md) | Component wrapping the month grids. |
| [`Nav`](../api/functions/Nav.md) | Render the toolbar with the navigation button. |
| [`NextMonthButton`](../api/functions/NextMonthButton.md) | Render the button to navigate to the next month. |
| [`Option`](../api/functions/Option.md) | Render the `option` element. |
| [`PreviousMonthButton`](../api/functions/PreviousMonthButton.md) | Render the button to navigate to the previous month. |
| [`Root`](../api/functions/Root.md) | Render the root element. |
| [`Select`](../api/functions/Select.md) | Render the `select` element. |
| [`Week`](../api/functions/Week.md) | Render a row in the calendar, with the days and the week number. |
| [`WeekNumber`](../api/functions/WeekNumber.md) | Render the cell with the number of the week. |
| [`WeekNumberHeader`](../api/functions/WeekNumberHeader.md) | Render the column header for the week numbers. |
| [`Weekday`](../api/functions/Weekday.md) | Render the column header with the weekday name (e.g. "Mo", "Tu", etc.). |
| [`Weekdays`](../api/functions/Weekdays.md) | Render the row with the weekday names. |
| [`Weeks`](../api/functions/Weeks.md) | Render the weeks in the month grid. |

</details>
## List of Custom Components

| Name | Description |
| ---------------------------------------------------------------- | -------------------------------------------------------------- |
| [`CaptionLabel`](../api/functions/CaptionLabel.md) | The caption label of the month grid. |
| [`Chevron`](../api/functions/Chevron.md) | The chevron icon used in the navigation buttons and dropdowns. |
| [`Day`](../api/functions/Day.md) | The day cell in the month grid. |
| [`DayButton`](../api/functions/DayButton.md) | The button containing the day in the day cell. |
| [`Dropdown`](../api/functions/Dropdown.md) | The dropdown element to select years and months. |
| [`DropdownNav`](../api/functions/DropdownNav.md) | The container of the dropdowns. |
| [`Footer`](../api/functions/Footer.md) | The footer element announced by screen readers. |
| [`Month`](../api/functions/Month.md) | The container of the MonthGrid. |
| [`MonthCaption`](../api/functions/MonthCaption.md) | The caption of the month grid. |
| [`MonthGrid`](../api/functions/MonthGrid.md) | The grid of days in a month. |
| [`Months`](../api/functions/Months.md) | Wrapper of the month grids. |
| [`MonthsDropdown`](../api/functions/MonthsDropdown.md) | The dropdown with the months. |
| [`Nav`](../api/functions/Nav.md) | The navigation element with the next and previous buttons. |
| [`NextMonthButton`](../api/functions/NextMonthButton.md) | The next month button element in the navigation. |
| [`Option`](../api/functions/Option.md) | The `<option>` HTML element in the dropdown. |
| [`PreviousMonthButton`](../api/functions/PreviousMonthButton.md) | The previous month button element in the navigation. |
| [`Root`](../api/functions/Root.md) | The root element of the calendar. |
| [`Select`](../api/functions/Select.md) | The select element in the dropdowns. |
| [`Week`](../api/functions/Week.md) | The week rows. |
| [`WeekNumber`](../api/functions/WeekNumber.md) | The cell with the number of the week. |
| [`WeekNumberHeader`](../api/functions/WeekNumberHeader.md) | The header of the week number column. |
| [`Weekday`](../api/functions/Weekday.md) | The weekday name in the header. |
| [`Weekdays`](../api/functions/Weekdays.md) | The row containing the week days. |
| [`Weeks`](../api/functions/Weeks.md) | The weeks section in the month grid. |
| [`YearsDropdown`](../api/functions/YearsDropdown.md) | The dropdown with the years. |

## Examples

Expand Down Expand Up @@ -121,10 +120,28 @@ export function MyDatePicker() {

## DayPicker Hook

In a custom component, you can implement the [`useDayPicker`](../api/functions/useDayPicker.md) hook to access the [DayPicker context](../api/type-aliases/DayPickerContext.md).

The DayPicker context provides the current state of the calendar, including the selected days, modifiers, and navigation state.
In a custom component, you can use the [`useDayPicker`](../api/functions/useDayPicker.md) hook to access the DayPicker context.

| Function | Return value | Description |
| :------------------------------------------------- | ------------------------------------------------------------- | :--------------------------------------------------------------------------- |
| [`useDayPicker`](../api/functions/useDayPicker.md) | [`DayPickerContext`](../api/type-aliases/DayPickerContext.md) | Return the current state of DayPicker and functions to navigate the calendar |

### DayPicker Context

The [DayPicker context](../api/type-aliases/DayPickerContext.md) provides the current state of the calendar, including the selected days, modifiers, and navigation state.

| Name | Type | Description |
| --------------- | ----------------------------------------------------------------------------- | ---------------------------------------------- |
| `classNames` | [`ClassNames`](../api/type-aliases/ClassNames.md) | The class names for the UI elements. |
| `components` | [`CustomComponents`](../api/type-aliases/CustomComponents.md) | The components used internally by DayPicker. |
| `formatters` | [`Formatters`](../api/type-aliases/Formatters.md) | The formatters used to format the UI elements. |
| `getModifiers` | (`day`) => [`Modifiers`](../api/type-aliases/Modifiers.md) | Returns the modifiers for the given day. |
| `goToMonth` | (`month`) => `void` | Navigate to the specified month. |
| `isSelected` | (`date`) => `boolean` \| `undefined` | Whether the given date is selected. |
| `labels` | [`Labels`](../api/type-aliases/Labels.md) | The labels used in the user interface. |
| `months` | [`CalendarMonth`](../api/classes/CalendarMonth.md)[] | The months displayed in the calendar. |
| `nextMonth` | `Date` \| `undefined` | The next month to display. |
| `previousMonth` | `Date` \| `undefined` | The previous month to display. |
| `select` | [`SelectHandler`](../api/type-aliases/SelectHandler.md)\<`T`\> \| `undefined` | Set a selection. |
| `selected` | [`SelectedValue`](../api/type-aliases/SelectedValue.md)\<`T`\> \| `undefined` | The selected date(s). |
| `styles` | `Partial`\<[`Styles`](../api/type-aliases/Styles.md)\> \| `undefined` | The styles for the UI elements. |