Skip to content

Commit 8d3df60

Browse files
authored
feat(datepicker): support inline variant without input (#286)
* feat(datepicker): support inline variant without input * docs: update props table on README
1 parent bb6ef6f commit 8d3df60

File tree

8 files changed

+139
-60
lines changed

8 files changed

+139
-60
lines changed

README.md

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@ It supports most props of Form.Input and Dayzed components. Check the [supported
1717
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
1818
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
1919

20-
- [Installation](#installation)
21-
- [Usage](#usage)
22-
- [Supported Props](#supported-props)
23-
- [Own Props](#own-props)
24-
- [Form.Input Props](#forminput-props)
25-
- [Dayzed Props](#dayzed-props)
26-
- [Customization](#customization)
27-
- [Roadmap](#roadmap)
28-
- [Contributors](#contributors)
29-
- [LICENSE](#license)
20+
- [📆 react-semantic-ui-datepickers](#-react-semantic-ui-datepickers)
21+
- [Overview](#overview)
22+
- [Table of Contents](#table-of-contents)
23+
- [Installation](#installation)
24+
- [Usage](#usage)
25+
- [Supported Props](#supported-props)
26+
- [Own Props](#own-props)
27+
- [Form.Input Props](#forminput-props)
28+
- [Dayzed Props](#dayzed-props)
29+
- [Customization](#customization)
30+
- [Roadmap](#roadmap)
31+
- [Contributors](#contributors)
32+
- [LICENSE](#license)
3033

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

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

7174
### Own Props
7275

73-
| property | type | required | default | description |
74-
| -------------------- | -------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
75-
| allowOnlyNumbers | boolean | no | true | Allows the user enter only numbers |
76-
| clearOnSameDateClick | boolean | no | true | Controls whether the datepicker's state resets if the same date is selected in succession. |
77-
| clearable | boolean | no | true | Allows the user to clear the value |
78-
| filterDate | function | no | () => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
79-
| 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) |
80-
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
81-
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
82-
| locale | string | no | 'en-US' | Filename of the locale to be used. PS: Feel free to submit PR's with more locales! |
83-
| onBlur | function | no | () => {} | Callback fired when the input loses focus |
84-
| onChange | function | no | () => {} | Callback fired when the value changes |
85-
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
86-
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
87-
| datePickerOnly | boolean | no | false | Allows the date to be selected only via the date picker and disables the text input |
76+
| property | type | required | default | description |
77+
| -------------------- | ------------ | -------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
78+
| allowOnlyNumbers | boolean | no | true | Allows the user enter only numbers |
79+
| autoComplete | string | no | -- | Specifies if the input should have autocomplete enabled |
80+
| clearOnSameDateClick | boolean | no | true | Controls whether the datepicker's state resets if the same date is selected in succession. |
81+
| clearable | boolean | no | true | Allows the user to clear the value |
82+
| filterDate | function | no | () => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
83+
| 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) |
84+
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
85+
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
86+
| inline | boolean | no | false | Uses an inline variant, without the input and the features related to it, e.g. clearable datepicker |
87+
| locale | string | no | 'en-US' | Filename of the locale to be used. PS: Feel free to submit PR's with more locales! |
88+
| onBlur | function | no | () => {} | Callback fired when the input loses focus |
89+
| onChange | function | no | () => {} | Callback fired when the value changes |
90+
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
91+
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
92+
| datePickerOnly | boolean | no | false | Allows the date to be selected only via the date picker and disables the text input |
93+
| value | Date\|Date[] | no | -- | The value of the datepicker |
8894

8995
### Form.Input Props
9096

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

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

src/__tests__/datepicker.test.tsx

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import localePt from '../locales/pt-BR.json';
66
import { getShortDate } from '../utils';
77
import DatePicker from '../';
88

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

1213
return {
1314
...options,
@@ -20,8 +21,8 @@ const setup = (props?: Partial<SemanticDatepickerProps>) => {
2021
options.rerender(
2122
<DatePicker onChange={jest.fn()} {...props} {...newProps} />
2223
),
23-
datePickerInput: options.getByTestId('datepicker-input')
24-
.firstChild as HTMLInputElement,
24+
datePickerInput: getQuery('datepicker-input')
25+
?.firstChild as HTMLInputElement,
2526
};
2627
};
2728
const onBlur = jest.fn();
@@ -37,10 +38,6 @@ afterEach(() => {
3738
});
3839

3940
describe('Basic datepicker', () => {
40-
it('renders', () => {
41-
expect(setup()).toBeTruthy();
42-
});
43-
4441
describe('reacts to keyboard events', () => {
4542
it('closes datepicker on Esc', async () => {
4643
const { getByText, openDatePicker, queryByText } = setup({ onBlur });
@@ -299,10 +296,6 @@ describe('Basic datepicker', () => {
299296
});
300297

301298
describe('Range datepicker', () => {
302-
it('renders', () => {
303-
expect(setup({ type: 'range' })).toBeTruthy();
304-
});
305-
306299
describe('reacts to keyboard events', () => {
307300
it('accepts valid input followed by Enter key', async () => {
308301
const { datePickerInput } = setup({ onBlur, type: 'range' });
@@ -435,3 +428,64 @@ describe('Range datepicker', () => {
435428
expect(datePickerInput.value).toBe('');
436429
});
437430
});
431+
432+
describe('Inline datepicker', () => {
433+
it('should not display the input when inline prop is true', () => {
434+
const { queryByTestId } = setup({ inline: true });
435+
436+
expect(queryByTestId('datepicker-input')).toBeFalsy();
437+
});
438+
439+
describe('basic variant', () => {
440+
it('fires onChange with event and selected date as arguments', async () => {
441+
const onChange = jest.fn();
442+
const today = getShortDate(new Date()) as string;
443+
const { getByTestId } = setup({ inline: true, onChange });
444+
const todayCell = getByTestId(RegExp(today));
445+
446+
fireEvent.click(todayCell);
447+
448+
expect(onChange).toHaveBeenNthCalledWith(
449+
1,
450+
expect.any(Object),
451+
expect.objectContaining({
452+
value: expect.any(Date),
453+
})
454+
);
455+
});
456+
});
457+
458+
describe('range variant', () => {
459+
it('fires onChange with event and selected dates as arguments', async () => {
460+
const onChange = jest.fn();
461+
const now = new Date();
462+
const today = getShortDate(now) as string;
463+
const tomorrow = getShortDate(
464+
new Date(now.setDate(now.getDate() + 1))
465+
) as string;
466+
const { getByTestId } = setup({ onChange, inline: true, type: 'range' });
467+
const todayCell = getByTestId(RegExp(today));
468+
const tomorrowCell = getByTestId(RegExp(tomorrow));
469+
470+
fireEvent.click(todayCell);
471+
472+
expect(onChange).toHaveBeenNthCalledWith(
473+
1,
474+
expect.any(Object),
475+
expect.objectContaining({
476+
value: [expect.any(Date)],
477+
})
478+
);
479+
480+
fireEvent.click(tomorrowCell);
481+
482+
expect(onChange).toHaveBeenNthCalledWith(
483+
2,
484+
expect.any(Object),
485+
expect.objectContaining({
486+
value: [expect.any(Date), expect.any(Date)],
487+
})
488+
);
489+
});
490+
});
491+
});

src/components/calendar/calendar.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
.clndr-calendars-segment {
22
text-align: center;
3-
position: absolute !important;
3+
margin-bottom: 0.25rem !important;
44
margin-top: 0.25rem !important;
5+
}
6+
7+
.clndr-calendars-segment.clndr-floating {
8+
position: absolute !important;
59
z-index: 2000;
610
}
711

src/components/calendar/calendar.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type CalendarProps = {
1414
getBackProps: (props: any) => void;
1515
getDateProps: (props: any) => void;
1616
getForwardProps: (props: any) => void;
17+
inline: SemanticDatepickerProps['inline'];
1718
maxDate?: Date;
1819
minDate?: Date;
1920
months: Locale['months'];
@@ -45,6 +46,7 @@ const Calendar: React.FC<CalendarProps> = ({
4546
getBackProps,
4647
getDateProps,
4748
getForwardProps,
49+
inline,
4850
maxDate,
4951
minDate,
5052
months,
@@ -57,7 +59,12 @@ const Calendar: React.FC<CalendarProps> = ({
5759
weekdays,
5860
pointing,
5961
}) => (
60-
<Segment className={cn('clndr-calendars-segment', pointings[pointing])}>
62+
<Segment
63+
className={cn('clndr-calendars-segment', {
64+
'clndr-floating': !inline,
65+
[pointings[pointing]]: !inline,
66+
})}
67+
>
6168
<div
6269
className="clndr-calendars-wrapper"
6370
style={{ '--n': calendars.length } as React.CSSProperties}

src/index.tsx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class SemanticDatepicker extends React.Component<
7676
firstDayOfWeek: 0,
7777
format: 'YYYY-MM-DD',
7878
id: undefined,
79+
inline: false,
7980
keepOpenOnClear: false,
8081
keepOpenOnSelect: false,
8182
label: undefined,
@@ -419,11 +420,34 @@ class SemanticDatepicker extends React.Component<
419420
clearable,
420421
pointing,
421422
filterDate,
423+
inline,
422424
readOnly,
423425
datePickerOnly,
424426
} = this.props;
427+
const datepickerComponent = (
428+
<this.Component
429+
{...this.dayzedProps}
430+
monthsToDisplay={this.isRangeInput ? 2 : 1}
431+
onChange={this.onDateSelected}
432+
selected={selectedDate}
433+
date={this.date}
434+
>
435+
{(props) => (
436+
<Calendar
437+
{...this.dayzedProps}
438+
{...props}
439+
{...locale}
440+
filterDate={filterDate}
441+
pointing={pointing}
442+
weekdays={this.weekdays}
443+
/>
444+
)}
445+
</this.Component>
446+
);
425447

426-
return (
448+
return inline ? (
449+
datepickerComponent
450+
) : (
427451
<div className="field" style={style} ref={this.el}>
428452
<Input
429453
{...this.inputProps}
@@ -437,26 +461,7 @@ class SemanticDatepicker extends React.Component<
437461
ref={this.inputRef}
438462
value={typedValue || selectedDateFormatted}
439463
/>
440-
{isVisible && (
441-
<this.Component
442-
{...this.dayzedProps}
443-
monthsToDisplay={this.isRangeInput ? 2 : 1}
444-
onChange={this.onDateSelected}
445-
selected={selectedDate}
446-
date={this.date}
447-
>
448-
{(props) => (
449-
<Calendar
450-
{...this.dayzedProps}
451-
{...props}
452-
{...locale}
453-
filterDate={filterDate}
454-
pointing={pointing}
455-
weekdays={this.weekdays}
456-
/>
457-
)}
458-
</this.Component>
459-
)}
464+
{isVisible && datepickerComponent}
460465
</div>
461466
);
462467
}

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export type SemanticDatepickerProps = PickedDayzedProps &
6363
format: string;
6464
keepOpenOnClear: boolean;
6565
keepOpenOnSelect: boolean;
66+
inline: boolean;
6667
locale: LocaleOptions;
6768
onBlur: (event?: React.SyntheticEvent) => void;
6869
onChange: (

stories/common.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const Content: React.FC<ContentProps> = ({ children, style }) => (
88
<div
99
style={{
1010
display: 'flex',
11+
flexDirection: 'column',
1112
position: 'absolute',
1213
height: '100%',
1314
width: '100%',

stories/index.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ stories.addParameters({
3434

3535
stories.add('Basic usage', () => {
3636
const type = select('Type', typeMap, typeMap.basic);
37+
const inline = boolean('Inline (without input)', false);
3738
const allowOnlyNumbers = boolean('Allow only numbers', false);
3839
const clearOnSameDateClick = boolean('Clear on same date click', true);
3940
const datePickerOnly = boolean('Datepicker only', false);
@@ -71,6 +72,7 @@ stories.add('Basic usage', () => {
7172
format={format}
7273
keepOpenOnClear={keepOpenOnClear}
7374
keepOpenOnSelect={keepOpenOnSelect}
75+
inline={inline}
7476
locale={locale}
7577
maxDate={maxDate}
7678
minDate={minDate}

0 commit comments

Comments
 (0)