Skip to content

Commit

Permalink
feat(datepicker): support inline variant without input (#286)
Browse files Browse the repository at this point in the history
* feat(datepicker): support inline variant without input

* docs: update props table on README
  • Loading branch information
arthurdenner committed Jun 4, 2020
1 parent bb6ef6f commit 8d3df60
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 60 deletions.
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

1 comment on commit 8d3df60

@vercel
Copy link

@vercel vercel bot commented on 8d3df60 Jun 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.