Skip to content

Commit

Permalink
feat: add MonthsDropdown and YearsDropdown custom components (#2454)
Browse files Browse the repository at this point in the history
* feat: add `MonthsDropdown` and `YearsDropdown` custom components

* Update docs

* Update docs
  • Loading branch information
gpbl committed Sep 15, 2024
1 parent 4514720 commit f79d58f
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 41 deletions.
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. |

0 comments on commit f79d58f

Please sign in to comment.