Skip to content
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
57 changes: 31 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ It supports most props of Form.Input and Dayzed components. Check the [supported
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Installation](#installation)
- [Usage](#usage)
- [Supported Props](#supported-props)
- [Own Props](#own-props)
- [Form.Input Props](#forminput-props)
- [Dayzed Props](#dayzed-props)
- [Customization](#customization)
- [Roadmap](#roadmap)
- [Contributors](#contributors)
- [LICENSE](#license)
- [📆 react-semantic-ui-datepickers](#-react-semantic-ui-datepickers)
- [Overview](#overview)
- [Table of Contents](#table-of-contents)
- [Installation](#installation)
- [Usage](#usage)
- [Supported Props](#supported-props)
- [Own Props](#own-props)
- [Form.Input Props](#forminput-props)
- [Dayzed Props](#dayzed-props)
- [Customization](#customization)
- [Roadmap](#roadmap)
- [Contributors](#contributors)
- [LICENSE](#license)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -70,21 +73,24 @@ More examples [here](https://react-semantic-ui-datepickers.now.sh).

### Own Props

| property | type | required | default | description |
| -------------------- | -------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
| allowOnlyNumbers | boolean | no | true | Allows the user enter only numbers |
| clearOnSameDateClick | boolean | no | true | Controls whether the datepicker's state resets if the same date is selected in succession. |
| clearable | boolean | no | true | Allows the user to clear the value |
| filterDate | function | no | () => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
| format | string | no | 'YYYY-MM-DD' | Specifies how the date will be formatted using the [date-fns' format](https://date-fns.org/v1.29.0/docs/format) |
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
| locale | string | no | 'en-US' | Filename of the locale to be used. PS: Feel free to submit PR's with more locales! |
| onBlur | function | no | () => {} | Callback fired when the input loses focus |
| onChange | function | no | () => {} | Callback fired when the value changes |
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
| datePickerOnly | boolean | no | false | Allows the date to be selected only via the date picker and disables the text input |
| property | type | required | default | description |
| -------------------- | ------------ | -------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
| allowOnlyNumbers | boolean | no | true | Allows the user enter only numbers |
| autoComplete | string | no | -- | Specifies if the input should have autocomplete enabled |
| clearOnSameDateClick | boolean | no | true | Controls whether the datepicker's state resets if the same date is selected in succession. |
| clearable | boolean | no | true | Allows the user to clear the value |
| filterDate | function | no | () => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
| format | string | no | 'YYYY-MM-DD' | Specifies how the date will be formatted using the [date-fns' format](https://date-fns.org/v1.29.0/docs/format) |
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
| inline | boolean | no | false | Uses an inline variant, without the input and the features related to it, e.g. clearable datepicker |
| locale | string | no | 'en-US' | Filename of the locale to be used. PS: Feel free to submit PR's with more locales! |
| onBlur | function | no | () => {} | Callback fired when the input loses focus |
| onChange | function | no | () => {} | Callback fired when the value changes |
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
| datePickerOnly | boolean | no | false | Allows the date to be selected only via the date picker and disables the text input |
| value | Date\|Date[] | no | -- | The value of the datepicker |

### Form.Input Props

Expand Down Expand Up @@ -129,7 +135,6 @@ In order to customize the elements, you can override the styles of the classes b
- Improve accessibility
> @donysukardi did some work on accessibility in the BaseDatePicker, but I couldn't get it working correcly. Feel free to help on this!
- Add more tests (including e2e)
> The current threshold is pretty useless 😕

> Feel free to open issues and/or create PRs to improve other aspects of the library!

Expand Down
76 changes: 65 additions & 11 deletions src/__tests__/datepicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import localePt from '../locales/pt-BR.json';
import { getShortDate } from '../utils';
import DatePicker from '../';

const setup = (props?: Partial<SemanticDatepickerProps>) => {
const setup = (props: Partial<SemanticDatepickerProps> = {}) => {
const options = render(<DatePicker onChange={jest.fn()} {...props} />);
const getQuery = props.inline ? options.queryByTestId : options.getByTestId;

return {
...options,
Expand All @@ -20,8 +21,8 @@ const setup = (props?: Partial<SemanticDatepickerProps>) => {
options.rerender(
<DatePicker onChange={jest.fn()} {...props} {...newProps} />
),
datePickerInput: options.getByTestId('datepicker-input')
.firstChild as HTMLInputElement,
datePickerInput: getQuery('datepicker-input')
?.firstChild as HTMLInputElement,
};
};
const onBlur = jest.fn();
Expand All @@ -37,10 +38,6 @@ afterEach(() => {
});

describe('Basic datepicker', () => {
it('renders', () => {
expect(setup()).toBeTruthy();
});

describe('reacts to keyboard events', () => {
it('closes datepicker on Esc', async () => {
const { getByText, openDatePicker, queryByText } = setup({ onBlur });
Expand Down Expand Up @@ -299,10 +296,6 @@ describe('Basic datepicker', () => {
});

describe('Range datepicker', () => {
it('renders', () => {
expect(setup({ type: 'range' })).toBeTruthy();
});

describe('reacts to keyboard events', () => {
it('accepts valid input followed by Enter key', async () => {
const { datePickerInput } = setup({ onBlur, type: 'range' });
Expand Down Expand Up @@ -435,3 +428,64 @@ describe('Range datepicker', () => {
expect(datePickerInput.value).toBe('');
});
});

describe('Inline datepicker', () => {
it('should not display the input when inline prop is true', () => {
const { queryByTestId } = setup({ inline: true });

expect(queryByTestId('datepicker-input')).toBeFalsy();
});

describe('basic variant', () => {
it('fires onChange with event and selected date as arguments', async () => {
const onChange = jest.fn();
const today = getShortDate(new Date()) as string;
const { getByTestId } = setup({ inline: true, onChange });
const todayCell = getByTestId(RegExp(today));

fireEvent.click(todayCell);

expect(onChange).toHaveBeenNthCalledWith(
1,
expect.any(Object),
expect.objectContaining({
value: expect.any(Date),
})
);
});
});

describe('range variant', () => {
it('fires onChange with event and selected dates as arguments', async () => {
const onChange = jest.fn();
const now = new Date();
const today = getShortDate(now) as string;
const tomorrow = getShortDate(
new Date(now.setDate(now.getDate() + 1))
) as string;
const { getByTestId } = setup({ onChange, inline: true, type: 'range' });
const todayCell = getByTestId(RegExp(today));
const tomorrowCell = getByTestId(RegExp(tomorrow));

fireEvent.click(todayCell);

expect(onChange).toHaveBeenNthCalledWith(
1,
expect.any(Object),
expect.objectContaining({
value: [expect.any(Date)],
})
);

fireEvent.click(tomorrowCell);

expect(onChange).toHaveBeenNthCalledWith(
2,
expect.any(Object),
expect.objectContaining({
value: [expect.any(Date), expect.any(Date)],
})
);
});
});
});
6 changes: 5 additions & 1 deletion src/components/calendar/calendar.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
.clndr-calendars-segment {
text-align: center;
position: absolute !important;
margin-bottom: 0.25rem !important;
margin-top: 0.25rem !important;
}

.clndr-calendars-segment.clndr-floating {
position: absolute !important;
z-index: 2000;
}

Expand Down
9 changes: 8 additions & 1 deletion src/components/calendar/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type CalendarProps = {
getBackProps: (props: any) => void;
getDateProps: (props: any) => void;
getForwardProps: (props: any) => void;
inline: SemanticDatepickerProps['inline'];
maxDate?: Date;
minDate?: Date;
months: Locale['months'];
Expand Down Expand Up @@ -45,6 +46,7 @@ const Calendar: React.FC<CalendarProps> = ({
getBackProps,
getDateProps,
getForwardProps,
inline,
maxDate,
minDate,
months,
Expand All @@ -57,7 +59,12 @@ const Calendar: React.FC<CalendarProps> = ({
weekdays,
pointing,
}) => (
<Segment className={cn('clndr-calendars-segment', pointings[pointing])}>
<Segment
className={cn('clndr-calendars-segment', {
'clndr-floating': !inline,
[pointings[pointing]]: !inline,
})}
>
<div
className="clndr-calendars-wrapper"
style={{ '--n': calendars.length } as React.CSSProperties}
Expand Down
47 changes: 26 additions & 21 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class SemanticDatepicker extends React.Component<
firstDayOfWeek: 0,
format: 'YYYY-MM-DD',
id: undefined,
inline: false,
keepOpenOnClear: false,
keepOpenOnSelect: false,
label: undefined,
Expand Down Expand Up @@ -419,11 +420,34 @@ class SemanticDatepicker extends React.Component<
clearable,
pointing,
filterDate,
inline,
readOnly,
datePickerOnly,
} = this.props;
const datepickerComponent = (
<this.Component
{...this.dayzedProps}
monthsToDisplay={this.isRangeInput ? 2 : 1}
onChange={this.onDateSelected}
selected={selectedDate}
date={this.date}
>
{(props) => (
<Calendar
{...this.dayzedProps}
{...props}
{...locale}
filterDate={filterDate}
pointing={pointing}
weekdays={this.weekdays}
/>
)}
</this.Component>
);

return (
return inline ? (
datepickerComponent
) : (
<div className="field" style={style} ref={this.el}>
<Input
{...this.inputProps}
Expand All @@ -437,26 +461,7 @@ class SemanticDatepicker extends React.Component<
ref={this.inputRef}
value={typedValue || selectedDateFormatted}
/>
{isVisible && (
<this.Component
{...this.dayzedProps}
monthsToDisplay={this.isRangeInput ? 2 : 1}
onChange={this.onDateSelected}
selected={selectedDate}
date={this.date}
>
{(props) => (
<Calendar
{...this.dayzedProps}
{...props}
{...locale}
filterDate={filterDate}
pointing={pointing}
weekdays={this.weekdays}
/>
)}
</this.Component>
)}
{isVisible && datepickerComponent}
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type SemanticDatepickerProps = PickedDayzedProps &
format: string;
keepOpenOnClear: boolean;
keepOpenOnSelect: boolean;
inline: boolean;
locale: LocaleOptions;
onBlur: (event?: React.SyntheticEvent) => void;
onChange: (
Expand Down
1 change: 1 addition & 0 deletions stories/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const Content: React.FC<ContentProps> = ({ children, style }) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
position: 'absolute',
height: '100%',
width: '100%',
Expand Down
2 changes: 2 additions & 0 deletions stories/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ stories.addParameters({

stories.add('Basic usage', () => {
const type = select('Type', typeMap, typeMap.basic);
const inline = boolean('Inline (without input)', false);
const allowOnlyNumbers = boolean('Allow only numbers', false);
const clearOnSameDateClick = boolean('Clear on same date click', true);
const datePickerOnly = boolean('Datepicker only', false);
Expand Down Expand Up @@ -71,6 +72,7 @@ stories.add('Basic usage', () => {
format={format}
keepOpenOnClear={keepOpenOnClear}
keepOpenOnSelect={keepOpenOnSelect}
inline={inline}
locale={locale}
maxDate={maxDate}
minDate={minDate}
Expand Down