Skip to content

Commit 83396e8

Browse files
fix(Datepicker): expose focus() and clear() methods via ref. (#1300)
Closes #1299
1 parent 31c9d9d commit 83396e8

File tree

2 files changed

+84
-22
lines changed

2 files changed

+84
-22
lines changed

src/components/Datepicker/Datepicker.spec.tsx

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { render, screen } from '@testing-library/react';
1+
import { act, render, renderHook, screen } from '@testing-library/react';
22
import { describe, expect, it, vi } from 'vitest';
3+
import type { DatepickerRef } from './Datepicker';
34
import { Datepicker } from './Datepicker';
45
import { getFormattedDate } from './helpers';
56
import userEvent from '@testing-library/user-event';
7+
import { useRef } from 'react';
68

79
describe('Components / Datepicker', () => {
810
it("should display today's date by default", () => {
@@ -74,4 +76,32 @@ describe('Components / Datepicker', () => {
7476
await userEvent.click(screen.getByRole('textbox'));
7577
await userEvent.click(document.body);
7678
});
79+
80+
it('should focus the input when ref.current.focus is called', () => {
81+
const {
82+
result: { current: ref },
83+
} = renderHook(() => useRef<DatepickerRef>(null));
84+
render(<Datepicker ref={ref} />);
85+
86+
act(() => ref.current?.focus());
87+
88+
expect(screen.getByRole('textbox')).toHaveFocus();
89+
});
90+
91+
it('should clear the value when ref.current.clear is called', async () => {
92+
const todaysDateInDefaultLanguage = getFormattedDate('en', new Date());
93+
const todaysDayOfMonth = new Date().getDate();
94+
const anotherDay = todaysDayOfMonth === 1 ? 2 : 1;
95+
96+
const {
97+
result: { current: ref },
98+
} = renderHook(() => useRef<DatepickerRef>(null));
99+
render(<Datepicker ref={ref} />);
100+
101+
await userEvent.click(screen.getByRole('textbox'));
102+
await userEvent.click(screen.getAllByText(anotherDay)[0]);
103+
act(() => ref.current?.clear());
104+
105+
expect(screen.getByDisplayValue(todaysDateInDefaultLanguage)).toBeInTheDocument();
106+
});
77107
});

src/components/Datepicker/Datepicker.tsx

+53-21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

3-
import type { FC, ReactNode } from 'react';
4-
import { useEffect, useRef, useState } from 'react';
3+
import type { ForwardRefRenderFunction, ReactNode } from 'react';
4+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
55
import { HiArrowLeft, HiArrowRight, HiCalendar } from 'react-icons/hi';
66
import { twMerge } from 'tailwind-merge';
77
import { mergeDeep } from '../../helpers/merge-deep';
@@ -71,6 +71,17 @@ export interface FlowbiteDatepickerPopupTheme {
7171
};
7272
}
7373

74+
export interface DatepickerRef {
75+
/**
76+
* Focus the datepicker input.
77+
*/
78+
focus: () => void;
79+
/**
80+
* Clears the datepicker value back to the defaultDate.
81+
*/
82+
clear: () => void;
83+
}
84+
7485
export interface DatepickerProps extends Omit<TextInputProps, 'theme'> {
7586
open?: boolean;
7687
inline?: boolean;
@@ -88,25 +99,28 @@ export interface DatepickerProps extends Omit<TextInputProps, 'theme'> {
8899
onSelectedDateChanged?: (date: Date) => void;
89100
}
90101

91-
export const Datepicker: FC<DatepickerProps> = ({
92-
title,
93-
open,
94-
inline = false,
95-
autoHide = true, // Hide when selected the day
96-
showClearButton = true,
97-
labelClearButton = 'Clear',
98-
showTodayButton = true,
99-
labelTodayButton = 'Today',
100-
defaultDate = new Date(),
101-
minDate,
102-
maxDate,
103-
language = 'en',
104-
weekStart = WeekStart.Sunday,
105-
className,
106-
theme: customTheme = {},
107-
onSelectedDateChanged,
108-
...props
109-
}) => {
102+
const DatepickerRender: ForwardRefRenderFunction<DatepickerRef, DatepickerProps> = (
103+
{
104+
title,
105+
open,
106+
inline = false,
107+
autoHide = true, // Hide when selected the day
108+
showClearButton = true,
109+
labelClearButton = 'Clear',
110+
showTodayButton = true,
111+
labelTodayButton = 'Today',
112+
defaultDate = new Date(),
113+
minDate,
114+
maxDate,
115+
language = 'en',
116+
weekStart = WeekStart.Sunday,
117+
className,
118+
theme: customTheme = {},
119+
onSelectedDateChanged,
120+
...props
121+
},
122+
ref,
123+
) => {
110124
const theme = mergeDeep(getTheme().datepicker, customTheme);
111125

112126
// Default date should respect the range
@@ -135,6 +149,22 @@ export const Datepicker: FC<DatepickerProps> = ({
135149
}
136150
};
137151

152+
const clearDate = () => {
153+
changeSelectedDate(defaultDate, true);
154+
if (defaultDate) {
155+
setViewDate(defaultDate);
156+
}
157+
};
158+
159+
useImperativeHandle(ref, () => ({
160+
focus() {
161+
inputRef.current?.focus();
162+
},
163+
clear() {
164+
clearDate();
165+
},
166+
}));
167+
138168
// Render the DatepickerView* node
139169
const renderView = (type: Views): ReactNode => {
140170
switch (type) {
@@ -325,4 +355,6 @@ export const Datepicker: FC<DatepickerProps> = ({
325355
);
326356
};
327357

358+
export const Datepicker = forwardRef(DatepickerRender);
359+
328360
Datepicker.displayName = 'Datepicker';

0 commit comments

Comments
 (0)