@acusti/date-picker 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,195 +0,0 @@
1
- import { Style } from '@acusti/styling';
2
- import clsx from 'clsx';
3
- import * as React from 'react';
4
-
5
- import { ROOT_CLASS_NAME, STYLES } from './styles/month-calendar.js';
6
- import {
7
- getDateFromMonthAndDay,
8
- getLastDateFromMonth,
9
- getMonthFromDate,
10
- getMonthNameFromMonth,
11
- getYearFromMonth,
12
- } from './utils.js';
13
-
14
- export type Props = {
15
- className?: string;
16
- dateEnd?: Date | null | number | string;
17
- dateEndPreview?: null | string;
18
- dateStart?: Date | null | number | string;
19
- isRange?: boolean;
20
- month: number; // a unique numerical value representing the number of months since jan 1970
21
- onChange?: (date: string) => void;
22
- onChangeEndPreview?: (date: string) => void;
23
- title?: string;
24
- };
25
-
26
- type DateRangeDays = [null | number, null | number, null | number];
27
-
28
- const { Fragment, useCallback } = React;
29
-
30
- const DAYS = Array(7).fill(null);
31
-
32
- export default function MonthCalendar({
33
- className,
34
- dateEnd,
35
- dateEndPreview,
36
- dateStart,
37
- isRange,
38
- month,
39
- onChange,
40
- onChangeEndPreview,
41
- title,
42
- }: Props) {
43
- const year = getYearFromMonth(month);
44
- title = title ?? `${getMonthNameFromMonth(month)} ${year}`;
45
- const firstDate = getDateFromMonthAndDay(month, 1);
46
- const lastDate = getLastDateFromMonth(month);
47
- const totalDays = lastDate.getDate();
48
- const firstDay = firstDate.getDay();
49
- const spacesAfterLastDay = 7 - (lastDate.getDay() % 7); // prettier-ignore
50
- const daySpaces = totalDays + firstDay + spacesAfterLastDay;
51
-
52
- const [dateRangeStartDay, dateRangeEndDay, dateRangeEndPreviewDay]: DateRangeDays = [
53
- dateStart,
54
- dateEnd,
55
- dateEndPreview,
56
- ].reduce(
57
- (acc: DateRangeDays, date, index) => {
58
- if (date != null && !(date instanceof Date)) {
59
- date = new Date(date);
60
- }
61
- if (date == null || Number.isNaN(date.getTime())) return acc;
62
-
63
- const dateMonth = getMonthFromDate(date);
64
- if (dateMonth < month) acc[index] = -1;
65
- else if (dateMonth > month) acc[index] = totalDays + 1;
66
- else acc[index] = date.getDate();
67
- if (index === 1) {
68
- const startDay = acc[index - 1];
69
- const endDay = acc[index];
70
- // Ensure that end date is after start date and swap them if not
71
- if (startDay != null && endDay != null && startDay > endDay) {
72
- acc[index - 1] = endDay;
73
- acc[index] = startDay;
74
- }
75
- }
76
-
77
- return acc;
78
- },
79
- [null, null, null],
80
- );
81
-
82
- const handleClickDay = useCallback(
83
- (event: React.SyntheticEvent<HTMLElement>) => {
84
- const { date } = event.currentTarget.dataset;
85
- if (date && onChange) onChange(date);
86
- },
87
- [onChange],
88
- );
89
-
90
- const handleMouseEnterDay = useCallback(
91
- (event: React.SyntheticEvent<HTMLElement>) => {
92
- if (isRange && onChangeEndPreview) {
93
- const { date } = event.currentTarget.dataset;
94
- if (date) onChangeEndPreview(date);
95
- }
96
- },
97
- [isRange, onChangeEndPreview],
98
- );
99
-
100
- return (
101
- <Fragment>
102
- <Style href="@acusti/date-picker/MonthCalendar">{STYLES}</Style>
103
- <div className={clsx(ROOT_CLASS_NAME, className)}>
104
- <div className={`${ROOT_CLASS_NAME}-month-title`}>
105
- <h3 className={`${ROOT_CLASS_NAME}-month-title-text`}>{title}</h3>
106
- </div>
107
- <div className={`${ROOT_CLASS_NAME}-month-week`}>
108
- <div className="week-day-item">
109
- <span className="week-day-item-text">Su</span>
110
- </div>
111
- <div className="week-day-item">
112
- <span className="week-day-item-text">Mo</span>
113
- </div>
114
- <div className="week-day-item">
115
- <span className="week-day-item-text">Tu</span>
116
- </div>
117
- <div className="week-day-item">
118
- <span className="week-day-item-text">We</span>
119
- </div>
120
- <div className="week-day-item">
121
- <span className="week-day-item-text">Th</span>
122
- </div>
123
- <div className="week-day-item">
124
- <span className="week-day-item-text">Fr</span>
125
- </div>
126
- <div className="week-day-item">
127
- <span className="week-day-item-text">Sa</span>
128
- </div>
129
- </div>
130
- <div className={`${ROOT_CLASS_NAME}-month-days`}>
131
- {Array(Math.floor(daySpaces / 7))
132
- .fill(null)
133
- .map((_, weekIndex) => (
134
- <div
135
- className={`${ROOT_CLASS_NAME}-month-row`}
136
- key={`MonthRow-${weekIndex}`}
137
- >
138
- {DAYS.map((__, dayIndex) => {
139
- dayIndex += weekIndex * 7;
140
- const dayNumber = (dayIndex - firstDay) + 1; // prettier-ignore
141
- const isEmpty =
142
- dayNumber < 1 || dayNumber > totalDays;
143
- const date = isEmpty
144
- ? null
145
- : getDateFromMonthAndDay(month, dayNumber);
146
- const isAfterDateRangeStart =
147
- dateRangeStartDay != null &&
148
- dayNumber > dateRangeStartDay;
149
- const isBeforeDateRangeEnd =
150
- (dateRangeEndDay == null &&
151
- dateRangeEndPreviewDay != null &&
152
- dayNumber < dateRangeEndPreviewDay) ||
153
- (dateRangeEndDay != null &&
154
- dayNumber < dateRangeEndDay);
155
-
156
- return (
157
- <button
158
- className={clsx(
159
- `${ROOT_CLASS_NAME}-month-day-item`,
160
- {
161
- 'end-date':
162
- !isEmpty &&
163
- dayNumber === dateRangeEndDay,
164
- 'is-empty': isEmpty,
165
- 'is-selected':
166
- !isEmpty &&
167
- isAfterDateRangeStart &&
168
- isBeforeDateRangeEnd,
169
- 'start-date':
170
- !isEmpty &&
171
- dayNumber === dateRangeStartDay,
172
- },
173
- )}
174
- data-date={date?.toISOString()}
175
- disabled={isEmpty}
176
- key={`MonthDayItem-${dayNumber}`}
177
- onClick={handleClickDay}
178
- onMouseEnter={handleMouseEnterDay}
179
- type="button"
180
- >
181
- {isEmpty ? null : (
182
- <span className="month-day-item-text">
183
- {dayNumber}
184
- </span>
185
- )}
186
- </button>
187
- );
188
- })}
189
- </div>
190
- ))}
191
- </div>
192
- </div>
193
- </Fragment>
194
- );
195
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { default as DatePicker } from './DatePicker.js';
2
- export { default as MonthCalendar } from './MonthCalendar.js';
3
- export * from './utils.js';
@@ -1,62 +0,0 @@
1
- export const ROOT_CLASS_NAME = 'uktdatepicker';
2
-
3
- export const STYLES = `
4
- .${ROOT_CLASS_NAME} {
5
- display: flex;
6
- box-sizing: border-box;
7
- padding: 40px 60px 60px;
8
- flex: 1 1 auto;
9
- position: relative;
10
- max-width: 450px;
11
- }
12
-
13
- .${ROOT_CLASS_NAME}.two-up {
14
- max-width: 820px;
15
- }
16
-
17
- .${ROOT_CLASS_NAME}-range-arrow-wrap {
18
- position: absolute;
19
- top: 30px;
20
- left: 0px;
21
- display: flex;
22
- justify-content: space-between;
23
- height: 0px;
24
- width: 100%;
25
- padding: 0px 60px;
26
- box-sizing: border-box;
27
- }
28
-
29
- .${ROOT_CLASS_NAME}-range-arrow {
30
- width: 35px;
31
- height: 35px;
32
- text-align: center;
33
- cursor: pointer;
34
- }
35
-
36
- .${ROOT_CLASS_NAME}-range-arrow.disabled {
37
- color: #ccc;
38
- cursor: default;
39
- }
40
-
41
- .${ROOT_CLASS_NAME}-range-arrow:active {
42
- transform: translateY(1px);
43
- }
44
-
45
- .${ROOT_CLASS_NAME}-range-arrow.left-arrow:after,
46
- .${ROOT_CLASS_NAME}-range-arrow.right-arrow:after {
47
- content: "‹";
48
- font-size: 24px;
49
- line-height: 35px;
50
- font-weight: bold;
51
- }
52
-
53
- .${ROOT_CLASS_NAME}-range-arrow.right-arrow:after {
54
- content: "›";
55
- }
56
-
57
- .${ROOT_CLASS_NAME}-month-container {
58
- display: flex;
59
- flex: 1 1 auto;
60
- justify-content: space-between;
61
- }
62
- `;
@@ -1,154 +0,0 @@
1
- export const ROOT_CLASS_NAME = 'uktmonthcalendar';
2
-
3
- export const STYLES = `
4
- .${ROOT_CLASS_NAME} {
5
- display: flex;
6
- flex-direction: column;
7
- flex: 1 1 auto;
8
- box-sizing: border-box;
9
- max-width: 325px;
10
- }
11
-
12
- .${ROOT_CLASS_NAME}-month-title {
13
- display: flex;
14
- align-items: center;
15
- justify-content: center;
16
- flex: 0 0 auto;
17
- box-sizing: border-box;
18
- padding-bottom: 25px;
19
- }
20
-
21
- h3.${ROOT_CLASS_NAME}-month-title-text {
22
- font-size: 18px;
23
- line-height: 23px;
24
- font-weight: 600;
25
- color: #000;
26
- margin: 0px;
27
- text-align: center;
28
- }
29
-
30
- .${ROOT_CLASS_NAME}-month-week {
31
- flex: 0 0 auto;
32
- display: grid;
33
- grid-column-gap: 0px;
34
- grid-template-columns: repeat(auto-fit, minmax(46px, 1fr));
35
- grid-auto-flow: dense;
36
- box-sizing: border-box;
37
- padding-bottom: 12px;
38
- }
39
-
40
- .${ROOT_CLASS_NAME}-month-week .week-day-item {
41
- flex: 1 1 auto;
42
- display: flex;
43
- justify-content: center;
44
- align-items: center;
45
- }
46
-
47
- .${ROOT_CLASS_NAME}-month-week span.week-day-item-text {
48
- text-align: center;
49
- font-size: 13px;
50
- line-height: 21px;
51
- margin: 0px;
52
- color: #9a9a9a;
53
- }
54
-
55
- .${ROOT_CLASS_NAME}-month-days {
56
- flex: 1 1 auto;
57
- display: flex;
58
- flex-direction: column;
59
- }
60
-
61
- .${ROOT_CLASS_NAME}-month-row {
62
- flex: 1 1 auto;
63
- display: grid;
64
- grid-column-gap: 0px;
65
- grid-template-columns: repeat(auto-fit, minmax(46px, 1fr));
66
- grid-auto-flow: dense;
67
- margin-bottom: 1px;
68
- }
69
-
70
- .${ROOT_CLASS_NAME}-month-day-item {
71
- display: flex;
72
- justify-content: center;
73
- align-items: center;
74
- position: relative;
75
- height: 46px;
76
- width: 46px;
77
- cursor: pointer;
78
- border: none;
79
- background-color: transparent;
80
- }
81
-
82
- .${ROOT_CLASS_NAME}-month-day-item:disabled {
83
- cursor: auto;
84
- }
85
-
86
- .${ROOT_CLASS_NAME}-month-day-item.is-selected {
87
- background-color: #f8f8f8;
88
- }
89
-
90
- .${ROOT_CLASS_NAME}-month-day-item.start-date {
91
- background-color: #f8f8f8;
92
- border-top-left-radius: 50%;
93
- border-bottom-left-radius: 50%;
94
- }
95
-
96
- .${ROOT_CLASS_NAME}-month-day-item.start-date:after {
97
- background-color: #000;
98
- opacity: 1;
99
- visibility: visible;
100
- }
101
- .${ROOT_CLASS_NAME}-month-day-item.start-date span.month-day-item-text {
102
- color: #fff;
103
- }
104
-
105
- .${ROOT_CLASS_NAME}-month-day-item.end-date {
106
- background-color: #f8f8f8;
107
- border-top-right-radius: 50%;
108
- border-bottom-right-radius: 50%;
109
- }
110
-
111
- .${ROOT_CLASS_NAME}-month-day-item.end-date:after {
112
- background-color: #000;
113
- opacity: 1;
114
- visibility: visible;
115
- }
116
- .${ROOT_CLASS_NAME}-month-day-item.end-date span.month-day-item-text {
117
- color: #fff;
118
- }
119
-
120
- .${ROOT_CLASS_NAME}-month-day-item:hover:after {
121
- opacity: 1;
122
- visibility: visible;
123
- }
124
-
125
- .${ROOT_CLASS_NAME}-month-day-item:after {
126
- content: "";
127
- position: absolute;
128
- left: 50%;
129
- top: 50%;
130
- transform: translate(-50%, -50%);
131
- pointer-events: none;
132
- border-radius: 50%;
133
- border: 1px solid #000;
134
- width: 43px;
135
- height: 43px;
136
- transition: opacity 0.25s ease-in-out;
137
- opacity: 0;
138
- visibility: hidden;
139
- }
140
-
141
- .${ROOT_CLASS_NAME}-month-day-item.is-empty:after {
142
- content: none;
143
- }
144
-
145
- .${ROOT_CLASS_NAME}-month-day-item span.month-day-item-text {
146
- text-align: center;
147
- font-size: 13px;
148
- line-height: 21px;
149
- margin: 0px;
150
- color: #000;
151
- position: relative;
152
- z-index: 1;
153
- }
154
- `;
package/src/utils.test.ts DELETED
@@ -1,163 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import {
4
- getDateFromMonthAndDay,
5
- getLastDateFromMonth,
6
- getMonthFromDate,
7
- getMonthNameFromMonth,
8
- getYearFromMonth,
9
- } from './utils.js';
10
-
11
- const INVALID_DATE = new Date('');
12
-
13
- describe('@acusti/date-picker', () => {
14
- describe('utils', () => {
15
- describe('getMonthFromDate', () => {
16
- it('returns the correct month digit for a post-unix epoch month', () => {
17
- expect(getMonthFromDate(new Date(1970, 0, 8))).toBe(0); // january 8, 1970
18
- expect(getMonthFromDate(new Date(1971, 11, 1))).toBe(23); // november 1, 1971
19
- });
20
-
21
- it('returns the correct month digit for a pre-unix epoch month', () => {
22
- expect(getMonthFromDate(new Date(1969, 11, 31))).toBe(-1); // december 31, 1969
23
- expect(getMonthFromDate(new Date(1968, 3, 30))).toBe(-21); // april 30, 1968
24
- });
25
-
26
- it('returns the correct month digit based on UTC time if asUTC is true', () => {
27
- const date = new Date('1970-01-01T00:00:00.000Z');
28
- expect(getMonthFromDate(date, true)).toBe(0);
29
- // if test is run in a timezone behind UTC, ommitting asUTC returns -1
30
- // if not, the month should be the same with or without asUTC flag
31
- expect(getMonthFromDate(date)).toBe(
32
- date.getTimezoneOffset() >= 60 ? -1 : 0,
33
- );
34
- });
35
-
36
- it('returns NaN for an Invalid Date', () => {
37
- expect(getMonthFromDate(INVALID_DATE)).toBe(NaN);
38
- });
39
- });
40
-
41
- describe('getYearFromMonth', () => {
42
- it('returns the correct year for a post-unix epoch date', () => {
43
- expect(getYearFromMonth(getMonthFromDate(new Date(1970, 0, 1)))).toBe(
44
- 1970,
45
- );
46
- expect(getYearFromMonth(getMonthFromDate(new Date(2048, 4, 31)))).toBe(
47
- 2048,
48
- );
49
- });
50
-
51
- it('returns the correct year digit for a pre-unix epoch date', () => {
52
- expect(getYearFromMonth(getMonthFromDate(new Date(1970, 0, 0)))).toBe(
53
- 1969,
54
- );
55
- expect(getYearFromMonth(getMonthFromDate(new Date(100, 11, 31)))).toBe(
56
- 100,
57
- );
58
- });
59
-
60
- it('returns NaN for an Invalid Date', () => {
61
- expect(getYearFromMonth(getMonthFromDate(INVALID_DATE))).toBe(NaN);
62
- });
63
- });
64
-
65
- describe('getMonthNameFromMonth', () => {
66
- it('returns the correct month name for a post-unix epoch month', () => {
67
- expect(
68
- getMonthNameFromMonth(getMonthFromDate(new Date(2023, 5, 19))),
69
- ).toBe('June');
70
- });
71
-
72
- it('returns the correct month name for a pre-unix epoch month', () => {
73
- expect(
74
- getMonthNameFromMonth(getMonthFromDate(new Date(1865, 5, 2))),
75
- ).toBe('June');
76
- });
77
-
78
- it('returns an empty string if given NaN (e.g. if dealing with an Invalid Date)', () => {
79
- expect(getMonthNameFromMonth(getMonthFromDate(INVALID_DATE))).toBe('');
80
- });
81
- });
82
-
83
- describe('getDateFromMonthAndDay', () => {
84
- it('returns the date of the specified day for a post-unix epoch month', () => {
85
- expect(
86
- getDateFromMonthAndDay(getMonthFromDate(new Date(2008, 2, 13)), 1),
87
- ).toEqual(new Date(2008, 2, 1));
88
- expect(
89
- getDateFromMonthAndDay(getMonthFromDate(new Date(1999, 11, 1)), 31),
90
- ).toEqual(new Date(1999, 11, 31));
91
- expect(
92
- getDateFromMonthAndDay(getMonthFromDate(new Date(2000, 0, 0)), 31),
93
- ).toEqual(new Date(1999, 11, 31));
94
- });
95
-
96
- it('returns the correct date for a pre-unix epoch month', () => {
97
- expect(
98
- getDateFromMonthAndDay(getMonthFromDate(new Date(1865, 5, 2)), 30),
99
- ).toEqual(new Date(1865, 5, 30));
100
- expect(
101
- getDateFromMonthAndDay(getMonthFromDate(new Date(101, 0, 0)), 1),
102
- ).toEqual(new Date(100, 11, 1));
103
- });
104
-
105
- it('returns date for start of day as UTC time if asUTC is true', () => {
106
- expect(
107
- getDateFromMonthAndDay(
108
- getMonthFromDate(new Date(1865, 5, 2)),
109
- 30,
110
- true,
111
- ),
112
- ).toEqual(new Date(Date.UTC(1865, 5, 30)));
113
- expect(
114
- getDateFromMonthAndDay(
115
- getMonthFromDate(new Date(101, 0, 0)),
116
- 1,
117
- true,
118
- ),
119
- ).toEqual(new Date(Date.UTC(100, 11, 1)));
120
- });
121
-
122
- it('returns an invalid date if given NaN (e.g. if dealing with an Invalid Date)', () => {
123
- expect(getDateFromMonthAndDay(getMonthFromDate(INVALID_DATE), 1)).toEqual(
124
- INVALID_DATE,
125
- );
126
- });
127
- });
128
-
129
- describe('getLastDateFromMonth', () => {
130
- it('returns the date of the last day for a post-unix epoch month', () => {
131
- expect(
132
- getLastDateFromMonth(getMonthFromDate(new Date(2008, 2, 13))),
133
- ).toEqual(new Date(2008, 2, 31));
134
- // february in a leap year
135
- expect(
136
- getLastDateFromMonth(getMonthFromDate(new Date(2024, 1, 23))),
137
- ).toEqual(new Date(2024, 1, 29));
138
- // february in a non-leap year
139
- expect(
140
- getLastDateFromMonth(getMonthFromDate(new Date(1985, 1, 1))),
141
- ).toEqual(new Date(1985, 1, 28));
142
- });
143
-
144
- it('returns the correct date for a pre-unix epoch month', () => {
145
- expect(
146
- getLastDateFromMonth(getMonthFromDate(new Date(1865, 5, 2))),
147
- ).toEqual(new Date(1865, 5, 30));
148
- });
149
-
150
- it('returns date for start of day as UTC time if asUTC is true', () => {
151
- expect(
152
- getLastDateFromMonth(getMonthFromDate(new Date(1865, 5, 2)), true),
153
- ).toEqual(new Date(Date.UTC(1865, 5, 30)));
154
- });
155
-
156
- it('returns an invalid date if given NaN (e.g. if dealing with an Invalid Date)', () => {
157
- expect(getLastDateFromMonth(getMonthFromDate(INVALID_DATE))).toEqual(
158
- INVALID_DATE,
159
- );
160
- });
161
- });
162
- });
163
- });
package/src/utils.ts DELETED
@@ -1,53 +0,0 @@
1
- // The following utils work on a “month” as a unique numerical value
2
- // representing the number of months since the unix epoch (jan 1970)
3
- const START_YEAR = 1970;
4
- const MONTH_NAMES = [
5
- 'January',
6
- 'February',
7
- 'March',
8
- 'April',
9
- 'May',
10
- 'June',
11
- 'July',
12
- 'August',
13
- 'September',
14
- 'October',
15
- 'November',
16
- 'December',
17
- ];
18
-
19
- const getYearFromDate = (date: Date, asUTC?: boolean) =>
20
- (asUTC ? date.getUTCFullYear() : date.getFullYear()) - START_YEAR;
21
-
22
- export const getMonthFromDate = (date: Date, asUTC?: boolean) => {
23
- const yearAsMonths = getYearFromDate(date, asUTC) * 12;
24
- return yearAsMonths + (asUTC ? date.getUTCMonth() : date.getMonth());
25
- };
26
-
27
- export const getYearFromMonth = (month: number) => Math.floor(month / 12) + START_YEAR;
28
-
29
- export const getMonthNameFromMonth = (month: number): string => {
30
- let index = month % 12;
31
- if (Number.isNaN(index)) return '';
32
- if (index < 0) index = 12 + index;
33
- return MONTH_NAMES[index];
34
- };
35
-
36
- export const getMonthAbbreviationFromMonth = (month: number) => {
37
- const monthName = getMonthNameFromMonth(month);
38
- if (monthName === 'September') return 'Sept';
39
- return monthName.substring(0, 3);
40
- };
41
-
42
- export const getDateFromMonthAndDay = (month: number, day: number, asUTC?: boolean) => {
43
- const monthIn12 = month < 0 ? (12 - Math.abs(month % 12)) % 12 : month % 12;
44
- const year = getYearFromMonth(month);
45
- return asUTC
46
- ? new Date(Date.UTC(year, monthIn12, day))
47
- : new Date(year, monthIn12, day);
48
- };
49
-
50
- export const getLastDateFromMonth = (month: number, asUTC?: boolean) => {
51
- // day 0 of the next month is the last day of the current month
52
- return getDateFromMonthAndDay(month + 1, 0, asUTC);
53
- };