@acusti/date-picker 0.0.3 → 0.0.5

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.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @acusti/date-picker
2
2
 
3
- [![latest version](https://img.shields.io/npm/v/@acusti/css-value-input?style=for-the-badge)](https://www.npmjs.com/package/@acusti/css-value-input)
4
- [![maintenance status](https://img.shields.io/npms-io/maintenance-score/@acusti/css-value-input?style=for-the-badge)](https://npms.io/search?q=%40acusti%2Fcss-value-input)
5
- [![bundle size](https://img.shields.io/bundlephobia/minzip/@acusti/css-value-input?style=for-the-badge)](https://bundlephobia.com/package/@acusti/css-value-input)
6
- [![downloads per month](https://img.shields.io/npm/dm/@acusti/css-value-input?style=for-the-badge)](https://www.npmjs.com/package/@acusti/css-value-input)
3
+ [![latest version](https://img.shields.io/npm/v/@acusti/date-picker?style=for-the-badge)](https://www.npmjs.com/package/@acusti/date-picker)
4
+ [![maintenance status](https://img.shields.io/npms-io/maintenance-score/@acusti/date-picker?style=for-the-badge)](https://npms.io/search?q=%40acusti%2Fdate-picker)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@acusti/date-picker?style=for-the-badge)](https://bundlephobia.com/package/@acusti/date-picker)
6
+ [![downloads per month](https://img.shields.io/npm/dm/@acusti/date-picker?style=for-the-badge)](https://www.npmjs.com/package/@acusti/date-picker)
7
7
 
8
8
  A group of React components and utils for rendering a date picker with
9
9
  support for ranges via a two-up month calendar view.
@@ -0,0 +1,12 @@
1
+ /// <reference types="react" />
2
+ export type Props = {
3
+ className?: string;
4
+ dateEnd?: Date | string | number;
5
+ dateStart?: Date | string | number;
6
+ initialMonth?: number;
7
+ isTwoUp?: boolean;
8
+ monthLimitFirst?: number;
9
+ monthLimitLast?: number;
10
+ useMonthAbbreviations?: boolean;
11
+ };
12
+ export default function DatePicker({ className, dateEnd, dateStart, isTwoUp, initialMonth, monthLimitFirst, monthLimitLast, useMonthAbbreviations, }: Props): JSX.Element;
@@ -0,0 +1,53 @@
1
+ import { Style } from '@acusti/styling';
2
+ import clsx from 'clsx';
3
+ import * as React from 'react';
4
+ import { getMonthAbbreviationFromMonth, getMonthFromDate, getYearFromMonth, } from './utils.js';
5
+ import { ROOT_CLASS_NAME, STYLES } from './styles/date-picker.js';
6
+ import MonthCalendar from './MonthCalendar.js';
7
+ const { Fragment, useCallback, useState } = React;
8
+ const getAbbreviatedMonthTitle = (month) => `${getMonthAbbreviationFromMonth(month)} ${getYearFromMonth(month)}`;
9
+ export default function DatePicker({ className, dateEnd, dateStart, isTwoUp, initialMonth, monthLimitFirst, monthLimitLast, useMonthAbbreviations, }) {
10
+ if (initialMonth == null) {
11
+ // Use dateStart if it’s set
12
+ const initialDate = dateStart == null ? new Date() : new Date(dateStart);
13
+ initialMonth = getMonthFromDate(initialDate);
14
+ if (isTwoUp && dateStart == null) {
15
+ initialMonth -= 1;
16
+ }
17
+ }
18
+ const [month, setMonth] = useState(initialMonth);
19
+ // In two-up view we see 1 more month, so monthLimitLast needs to be 1 less
20
+ if (isTwoUp && monthLimitLast != null) {
21
+ monthLimitLast -= 1;
22
+ }
23
+ const handleClickLeftArrow = useCallback(() => {
24
+ setMonth((existingMonth) => monthLimitFirst == null || existingMonth > monthLimitFirst
25
+ ? existingMonth - 1
26
+ : existingMonth);
27
+ }, [monthLimitFirst]);
28
+ const handleClickRightArrow = useCallback(() => {
29
+ setMonth((existingMonth) => monthLimitLast == null || existingMonth < monthLimitLast
30
+ ? existingMonth + 1
31
+ : existingMonth);
32
+ }, [monthLimitLast]);
33
+ return (React.createElement(Fragment, null,
34
+ React.createElement(Style, null, STYLES),
35
+ React.createElement("div", { className: clsx(ROOT_CLASS_NAME, className, {
36
+ 'two-up': isTwoUp,
37
+ }) },
38
+ React.createElement("div", { className: `${ROOT_CLASS_NAME}-range-arrow-wrap` },
39
+ React.createElement("div", { className: clsx(`${ROOT_CLASS_NAME}-range-arrow left-arrow`, {
40
+ disabled: monthLimitFirst != null && month <= monthLimitFirst,
41
+ }), onClick: handleClickLeftArrow }),
42
+ React.createElement("div", { className: clsx(`${ROOT_CLASS_NAME}-range-arrow right-arrow`, {
43
+ disabled: monthLimitLast != null && month >= monthLimitLast,
44
+ }), onClick: handleClickRightArrow })),
45
+ React.createElement("div", { className: `${ROOT_CLASS_NAME}-month-container` },
46
+ React.createElement(MonthCalendar, { dateEnd: dateEnd, dateStart: dateStart, month: month, title: useMonthAbbreviations
47
+ ? getAbbreviatedMonthTitle(month)
48
+ : undefined }),
49
+ isTwoUp ? (React.createElement(MonthCalendar, { dateEnd: dateEnd, dateStart: dateStart, month: month + 1, title: useMonthAbbreviations
50
+ ? getAbbreviatedMonthTitle(month + 1)
51
+ : undefined })) : null))));
52
+ }
53
+ //# sourceMappingURL=DatePicker.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Flowtype definitions for DatePicker
3
+ * Generated by Flowgen from a Typescript Definition
4
+ * Flowgen v1.20.1
5
+ * @flow
6
+ */
7
+
8
+ export type Props = {|
9
+ className?: string,
10
+ dateEnd?: Date | string | number,
11
+ dateStart?: Date | string | number,
12
+ initialMonth?: number,
13
+ isTwoUp?: boolean,
14
+ monthLimitFirst?: number,
15
+ monthLimitLast?: number,
16
+ useMonthAbbreviations?: boolean,
17
+ |};
18
+ declare export default function DatePicker(x: Props): React$Node;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatePicker.js","sourceRoot":"","sources":["../src/DatePicker.tsx"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import * as React from 'react';
2
2
  export type Props = {
3
3
  className?: string;
4
+ dateStart?: Date | string | number;
5
+ dateEnd?: Date | string | number;
4
6
  month: number;
5
7
  onChange?: (event: React.SyntheticEvent<HTMLElement>) => void;
6
8
  title?: string;
7
9
  };
8
- export default function MonthCalendar({ className, month, onChange, title }: Props): JSX.Element;
10
+ export default function MonthCalendar({ className, dateEnd, dateStart, month, onChange, title, }: Props): JSX.Element;
@@ -1,11 +1,11 @@
1
1
  import { Style } from '@acusti/styling';
2
2
  import clsx from 'clsx';
3
3
  import * as React from 'react';
4
- import { getMonthNameFromMonth, getYearFromMonth } from './utils.js';
4
+ import { getMonthFromDate, getMonthNameFromMonth, getYearFromMonth } from './utils.js';
5
5
  import { ROOT_CLASS_NAME, STYLES } from './styles/month-calendar.js';
6
6
  const { Fragment, useCallback } = React;
7
7
  const DAYS = Array(7).fill(null);
8
- export default function MonthCalendar({ className, month, onChange, title }) {
8
+ export default function MonthCalendar({ className, dateEnd, dateStart, month, onChange, title, }) {
9
9
  const year = getYearFromMonth(month);
10
10
  title = title || `${getMonthNameFromMonth(month)} ${year}`;
11
11
  const monthWithinYear = month % 12;
@@ -17,6 +17,33 @@ export default function MonthCalendar({ className, month, onChange, title }) {
17
17
  const firstDay = firstDate.getDay();
18
18
  const spacesAfterLastDay = 7 - (lastDate.getDay() % 7); // prettier-ignore
19
19
  const daySpaces = totalDays + firstDay + spacesAfterLastDay;
20
+ const [dateRangeStartDay, dateRangeEndDay] = [
21
+ dateStart,
22
+ dateEnd,
23
+ ].reduce((acc, date, index) => {
24
+ if (date != null && !(date instanceof Date)) {
25
+ date = new Date(date);
26
+ }
27
+ if (date == null || Number.isNaN(date.getTime()))
28
+ return acc;
29
+ const dateMonth = getMonthFromDate(date);
30
+ if (dateMonth < month)
31
+ acc[index] = -1;
32
+ else if (dateMonth > month)
33
+ acc[index] = totalDays + 1;
34
+ else
35
+ acc[index] = date.getDate();
36
+ if (index === 1) {
37
+ const startDay = acc[index - 1];
38
+ const endDay = acc[index];
39
+ // Ensure that end date is after start date and swap them if not
40
+ if (startDay != null && endDay != null && startDay > endDay) {
41
+ acc[index - 1] = endDay;
42
+ acc[index] = startDay;
43
+ }
44
+ }
45
+ return acc;
46
+ }, [null, null]);
20
47
  const handleClickDay = useCallback((event) => {
21
48
  if (onChange)
22
49
  onChange(event);
@@ -41,15 +68,26 @@ export default function MonthCalendar({ className, month, onChange, title }) {
41
68
  React.createElement("span", { className: "week-day-item-text" }, "Fr")),
42
69
  React.createElement("div", { className: "week-day-item" },
43
70
  React.createElement("span", { className: "week-day-item-text" }, "Sa"))),
44
- React.createElement("div", { className: `${ROOT_CLASS_NAME}-month-days` }, Array(Math.ceil(daySpaces / 7))
71
+ React.createElement("div", { className: `${ROOT_CLASS_NAME}-month-days` }, Array(Math.floor(daySpaces / 7))
45
72
  .fill(null)
46
73
  .map((_, weekIndex) => (React.createElement("div", { className: `${ROOT_CLASS_NAME}-month-row`, key: `MonthRow-${weekIndex}` }, DAYS.map((_, dayIndex) => {
47
74
  dayIndex += weekIndex * 7;
48
75
  const dayNumber = (dayIndex - firstDay) + 1; // prettier-ignore
49
- return (React.createElement("div", { className: `${ROOT_CLASS_NAME}-month-day-item`, key: `MonthDayItem-${dayNumber}`, onClick: handleClickDay },
50
- React.createElement("span", { className: "month-day-item-text" }, dayNumber < 1 || dayNumber > totalDays
51
- ? ''
52
- : dayNumber)));
76
+ const isEmpty = dayNumber < 1 || dayNumber > totalDays;
77
+ const isAfterDateRangeStart = dateRangeStartDay != null &&
78
+ dayNumber > dateRangeStartDay;
79
+ const isBeforeDateRangeEnd = dateRangeEndDay != null &&
80
+ dayNumber < dateRangeEndDay;
81
+ return (React.createElement("div", { className: clsx(`${ROOT_CLASS_NAME}-month-day-item`, {
82
+ 'is-empty': isEmpty,
83
+ 'is-selected': !isEmpty &&
84
+ isAfterDateRangeStart &&
85
+ isBeforeDateRangeEnd,
86
+ 'end-date': !isEmpty &&
87
+ dayNumber === dateRangeEndDay,
88
+ 'start-date': !isEmpty &&
89
+ dayNumber === dateRangeStartDay,
90
+ }), key: `MonthDayItem-${dayNumber}`, onClick: handleClickDay }, isEmpty ? null : (React.createElement("span", { className: "month-day-item-text" }, dayNumber))));
53
91
  }))))))));
54
92
  }
55
93
  //# sourceMappingURL=MonthCalendar.js.map
@@ -8,6 +8,8 @@
8
8
  import * as React from "react";
9
9
  export type Props = {|
10
10
  className?: string,
11
+ dateStart?: Date | string | number,
12
+ dateEnd?: Date | string | number,
11
13
  month: number,
12
14
  onChange?: (event: SyntheticEvent<HTMLElement>) => void,
13
15
  title?: string,
@@ -1 +1 @@
1
- {"version":3,"file":"MonthCalendar.js","sourceRoot":"","sources":["../src/MonthCalendar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AASrE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;AAExC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEjC,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAS;IAC9E,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACrC,KAAK,GAAG,KAAK,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;IAC3D,MAAM,eAAe,GAAG,KAAK,GAAG,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,KAAK,GAAG,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1E,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;IACpC,MAAM,kBAAkB,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB;IAC1E,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,kBAAkB,CAAC;IAE5D,MAAM,cAAc,GAAG,WAAW,CAC9B,CAAC,KAAwC,EAAE,EAAE;QACzC,IAAI,QAAQ;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,EACD,CAAC,QAAQ,CAAC,CACb,CAAC;IAEF,OAAO,CACH,oBAAC,QAAQ;QACL,oBAAC,KAAK,QAAE,MAAM,CAAS;QACvB,6BAAK,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC;YAC5C,6BAAK,SAAS,EAAE,GAAG,eAAe,cAAc;gBAC5C,4BAAI,SAAS,EAAE,GAAG,eAAe,mBAAmB,IAAG,KAAK,CAAM,CAChE;YACN,6BAAK,SAAS,EAAE,GAAG,eAAe,aAAa;gBAC3C,6BAAK,SAAS,EAAC,eAAe;oBAC1B,8BAAM,SAAS,EAAC,oBAAoB,SAAU,CAC5C;gBACN,6BAAK,SAAS,EAAC,eAAe;oBAC1B,8BAAM,SAAS,EAAC,oBAAoB,SAAU,CAC5C;gBACN,6BAAK,SAAS,EAAC,eAAe;oBAC1B,8BAAM,SAAS,EAAC,oBAAoB,SAAU,CAC5C;gBACN,6BAAK,SAAS,EAAC,eAAe;oBAC1B,8BAAM,SAAS,EAAC,oBAAoB,SAAU,CAC5C;gBACN,6BAAK,SAAS,EAAC,eAAe;oBAC1B,8BAAM,SAAS,EAAC,oBAAoB,SAAU,CAC5C;gBACN,6BAAK,SAAS,EAAC,eAAe;oBAC1B,8BAAM,SAAS,EAAC,oBAAoB,SAAU,CAC5C;gBACN,6BAAK,SAAS,EAAC,eAAe;oBAC1B,8BAAM,SAAS,EAAC,oBAAoB,SAAU,CAC5C,CACJ;YACN,6BAAK,SAAS,EAAE,GAAG,eAAe,aAAa,IAC1C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;iBAC3B,IAAI,CAAC,IAAI,CAAC;iBACV,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,CAAC,CACnB,6BACI,SAAS,EAAE,GAAG,eAAe,YAAY,EACzC,GAAG,EAAE,YAAY,SAAS,EAAE,IAE3B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE;gBACtB,QAAQ,IAAI,SAAS,GAAG,CAAC,CAAC;gBAC1B,MAAM,SAAS,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB;gBAC/D,OAAO,CACH,6BACI,SAAS,EAAE,GAAG,eAAe,iBAAiB,EAC9C,GAAG,EAAE,gBAAgB,SAAS,EAAE,EAChC,OAAO,EAAE,cAAc;oBAEvB,8BAAM,SAAS,EAAC,qBAAqB,IAChC,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,SAAS;wBACnC,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,SAAS,CACZ,CACL,CACT,CAAC;YACN,CAAC,CAAC,CACA,CACT,CAAC,CACJ,CACJ,CACC,CACd,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"MonthCalendar.js","sourceRoot":"","sources":["../src/MonthCalendar.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const ROOT_CLASS_NAME = "uktdatepicker";
2
+ export declare const STYLES: string;
@@ -0,0 +1,62 @@
1
+ export const ROOT_CLASS_NAME = 'uktdatepicker';
2
+ export const STYLES = `
3
+ .${ROOT_CLASS_NAME} {
4
+ display: flex;
5
+ box-sizing: border-box;
6
+ padding: 40px 60px 60px;
7
+ flex: 1 1 auto;
8
+ position: relative;
9
+ max-width: 450px;
10
+ }
11
+
12
+ .${ROOT_CLASS_NAME}.two-up {
13
+ max-width: 820px;
14
+ }
15
+
16
+ .${ROOT_CLASS_NAME}-range-arrow-wrap {
17
+ position: absolute;
18
+ top: 30px;
19
+ left: 0px;
20
+ display: flex;
21
+ justify-content: space-between;
22
+ height: 0px;
23
+ width: 100%;
24
+ padding: 0px 60px;
25
+ box-sizing: border-box;
26
+ }
27
+
28
+ .${ROOT_CLASS_NAME}-range-arrow {
29
+ width: 35px;
30
+ height: 35px;
31
+ text-align: center;
32
+ cursor: pointer;
33
+ }
34
+
35
+ .${ROOT_CLASS_NAME}-range-arrow.disabled {
36
+ color: #ccc;
37
+ cursor: default;
38
+ }
39
+
40
+ .${ROOT_CLASS_NAME}-range-arrow:active {
41
+ transform: translateY(1px);
42
+ }
43
+
44
+ .${ROOT_CLASS_NAME}-range-arrow.left-arrow:after,
45
+ .${ROOT_CLASS_NAME}-range-arrow.right-arrow:after {
46
+ content: "‹";
47
+ font-size: 24px;
48
+ line-height: 35px;
49
+ font-weight: bold;
50
+ }
51
+
52
+ .${ROOT_CLASS_NAME}-range-arrow.right-arrow:after {
53
+ content: "›";
54
+ }
55
+
56
+ .${ROOT_CLASS_NAME}-month-container {
57
+ display: flex;
58
+ flex: 1 1 auto;
59
+ justify-content: space-between;
60
+ }
61
+ `;
62
+ //# sourceMappingURL=date-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date-picker.js","sourceRoot":"","sources":["../../src/styles/date-picker.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAC;AAE/C,MAAM,CAAC,MAAM,MAAM,GAAG;GACnB,eAAe;;;;;;;;;GASf,eAAe;;;;GAIf,eAAe;;;;;;;;;;;;GAYf,eAAe;;;;;;;GAOf,eAAe;;;;;GAKf,eAAe;;;;GAIf,eAAe;GACf,eAAe;;;;;;;GAOf,eAAe;;;;GAIf,eAAe;;;;;CAKjB,CAAC"}
@@ -76,7 +76,7 @@ h3.${ROOT_CLASS_NAME}-month-title-text {
76
76
  cursor: pointer;
77
77
  }
78
78
 
79
- .${ROOT_CLASS_NAME}-month-day-item.selected {
79
+ .${ROOT_CLASS_NAME}-month-day-item.is-selected {
80
80
  background-color: #f8f8f8;
81
81
  }
82
82
 
@@ -131,6 +131,10 @@ h3.${ROOT_CLASS_NAME}-month-title-text {
131
131
  visibility: hidden;
132
132
  }
133
133
 
134
+ .${ROOT_CLASS_NAME}-month-day-item.is-empty:after {
135
+ content: none;
136
+ }
137
+
134
138
  .${ROOT_CLASS_NAME}-month-day-item span.month-day-item-text {
135
139
  text-align: center;
136
140
  font-size: 13px;
@@ -1 +1 @@
1
- {"version":3,"file":"month-calendar.js","sourceRoot":"","sources":["../../src/styles/month-calendar.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAElD,MAAM,CAAC,MAAM,MAAM,GAAG;GACnB,eAAe;;;;;;;;GAQf,eAAe;;;;;;;;;KASb,eAAe;;;;;;;;;GASjB,eAAe;;;;;;;;;;GAUf,eAAe;;;;;;;GAOf,eAAe;;;;;;;;GAQf,eAAe;;;;;;GAMf,eAAe;;;;;;;;;GASf,eAAe;;;;;;;;;;GAUf,eAAe;;;;GAIf,eAAe;;;;;;GAMf,eAAe;;;;;GAKf,eAAe;;;;GAIf,eAAe;;;;;;GAMf,eAAe;;;;;GAKf,eAAe;;;;GAIf,eAAe;;;;;GAKf,eAAe;;;;;;;;;;;;;;;;GAgBf,eAAe;;;;;;;;;CASjB,CAAC"}
1
+ {"version":3,"file":"month-calendar.js","sourceRoot":"","sources":["../../src/styles/month-calendar.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAElD,MAAM,CAAC,MAAM,MAAM,GAAG;GACnB,eAAe;;;;;;;;GAQf,eAAe;;;;;;;;;KASb,eAAe;;;;;;;;;GASjB,eAAe;;;;;;;;;;GAUf,eAAe;;;;;;;GAOf,eAAe;;;;;;;;GAQf,eAAe;;;;;;GAMf,eAAe;;;;;;;;;GASf,eAAe;;;;;;;;;;GAUf,eAAe;;;;GAIf,eAAe;;;;;;GAMf,eAAe;;;;;GAKf,eAAe;;;;GAIf,eAAe;;;;;;GAMf,eAAe;;;;;GAKf,eAAe;;;;GAIf,eAAe;;;;;GAKf,eAAe;;;;;;;;;;;;;;;;GAgBf,eAAe;;;;GAIf,eAAe;;;;;;;;;CASjB,CAAC"}
package/dist/utils.js CHANGED
@@ -18,7 +18,14 @@ const MONTH_NAMES = [
18
18
  const getYearFromDate = (date) => date.getFullYear() - START_YEAR;
19
19
  export const getMonthFromDate = (date) => date.getMonth() + (getYearFromDate(date) * 12); // prettier-ignore
20
20
  export const getYearFromMonth = (month) => Math.floor(month / 12) + START_YEAR;
21
- export const getMonthNameFromMonth = (month) => MONTH_NAMES[month % 12];
21
+ export const getMonthNameFromMonth = (month) => {
22
+ let index = month % 12;
23
+ if (Number.isNaN(index))
24
+ return '';
25
+ if (index < 0)
26
+ index = 12 + index;
27
+ return MONTH_NAMES[index];
28
+ };
22
29
  export const getMonthAbbreviationFromMonth = (month) => {
23
30
  const monthName = getMonthNameFromMonth(month);
24
31
  if (monthName === 'September')
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,oEAAoE;AACpE,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,WAAW,GAAG;IAChB,SAAS;IACT,UAAU;IACV,OAAO;IACP,OAAO;IACP,KAAK;IACL,MAAM;IACN,MAAM;IACN,QAAQ;IACR,WAAW;IACX,SAAS;IACT,UAAU;IACV,UAAU;CACb,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,IAAU,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,UAAU,CAAC;AAExE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAE,EAAE,CAC3C,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,kBAAkB;AAEtE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,UAAU,CAAC;AAEvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;AAEhF,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,KAAa,EAAE,EAAE;IAC3D,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC/C,IAAI,SAAS,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC;IAC7C,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACrC,CAAC,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,oEAAoE;AACpE,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,WAAW,GAAG;IAChB,SAAS;IACT,UAAU;IACV,OAAO;IACP,OAAO;IACP,KAAK;IACL,MAAM;IACN,MAAM;IACN,QAAQ;IACR,WAAW;IACX,SAAS;IACT,UAAU;IACV,UAAU;CACb,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,IAAU,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,UAAU,CAAC;AAExE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAU,EAAE,EAAE,CAC3C,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,kBAAkB;AAEtE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,UAAU,CAAC;AAEvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAa,EAAU,EAAE;IAC3D,IAAI,KAAK,GAAG,KAAK,GAAG,EAAE,CAAC;IACvB,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,KAAK,GAAG,CAAC;QAAE,KAAK,GAAG,EAAE,GAAG,KAAK,CAAC;IAClC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,KAAa,EAAE,EAAE;IAC3D,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC/C,IAAI,SAAS,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC;IAC7C,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACrC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acusti/date-picker",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "React component that renders a date picker with the option to choose a range",
5
5
  "keywords": [
6
6
  "react",
@@ -0,0 +1,119 @@
1
+ import { Style } from '@acusti/styling';
2
+ import clsx from 'clsx';
3
+ import * as React from 'react';
4
+
5
+ import {
6
+ getMonthAbbreviationFromMonth,
7
+ getMonthFromDate,
8
+ getYearFromMonth,
9
+ } from './utils.js';
10
+ import { ROOT_CLASS_NAME, STYLES } from './styles/date-picker.js';
11
+
12
+ import MonthCalendar from './MonthCalendar.js';
13
+
14
+ export type Props = {
15
+ className?: string;
16
+ dateEnd?: Date | string | number;
17
+ dateStart?: Date | string | number;
18
+ initialMonth?: number;
19
+ isTwoUp?: boolean;
20
+ monthLimitFirst?: number;
21
+ monthLimitLast?: number;
22
+ useMonthAbbreviations?: boolean;
23
+ };
24
+
25
+ const { Fragment, useCallback, useState } = React;
26
+
27
+ const getAbbreviatedMonthTitle = (month: number) =>
28
+ `${getMonthAbbreviationFromMonth(month)} ${getYearFromMonth(month)}`;
29
+
30
+ export default function DatePicker({
31
+ className,
32
+ dateEnd,
33
+ dateStart,
34
+ isTwoUp,
35
+ initialMonth,
36
+ monthLimitFirst,
37
+ monthLimitLast,
38
+ useMonthAbbreviations,
39
+ }: Props) {
40
+ if (initialMonth == null) {
41
+ // Use dateStart if it’s set
42
+ const initialDate = dateStart == null ? new Date() : new Date(dateStart);
43
+ initialMonth = getMonthFromDate(initialDate);
44
+ if (isTwoUp && dateStart == null) {
45
+ initialMonth -= 1;
46
+ }
47
+ }
48
+ const [month, setMonth] = useState(initialMonth);
49
+ // In two-up view we see 1 more month, so monthLimitLast needs to be 1 less
50
+ if (isTwoUp && monthLimitLast != null) {
51
+ monthLimitLast -= 1;
52
+ }
53
+
54
+ const handleClickLeftArrow = useCallback(() => {
55
+ setMonth((existingMonth) =>
56
+ monthLimitFirst == null || existingMonth > monthLimitFirst
57
+ ? existingMonth - 1
58
+ : existingMonth,
59
+ );
60
+ }, [monthLimitFirst]);
61
+
62
+ const handleClickRightArrow = useCallback(() => {
63
+ setMonth((existingMonth) =>
64
+ monthLimitLast == null || existingMonth < monthLimitLast
65
+ ? existingMonth + 1
66
+ : existingMonth,
67
+ );
68
+ }, [monthLimitLast]);
69
+
70
+ return (
71
+ <Fragment>
72
+ <Style>{STYLES}</Style>
73
+ <div
74
+ className={clsx(ROOT_CLASS_NAME, className, {
75
+ 'two-up': isTwoUp,
76
+ })}
77
+ >
78
+ <div className={`${ROOT_CLASS_NAME}-range-arrow-wrap`}>
79
+ <div
80
+ className={clsx(`${ROOT_CLASS_NAME}-range-arrow left-arrow`, {
81
+ disabled: monthLimitFirst != null && month <= monthLimitFirst,
82
+ })}
83
+ onClick={handleClickLeftArrow}
84
+ />
85
+ <div
86
+ className={clsx(`${ROOT_CLASS_NAME}-range-arrow right-arrow`, {
87
+ disabled: monthLimitLast != null && month >= monthLimitLast,
88
+ })}
89
+ onClick={handleClickRightArrow}
90
+ />
91
+ </div>
92
+ <div className={`${ROOT_CLASS_NAME}-month-container`}>
93
+ <MonthCalendar
94
+ dateEnd={dateEnd}
95
+ dateStart={dateStart}
96
+ month={month}
97
+ title={
98
+ useMonthAbbreviations
99
+ ? getAbbreviatedMonthTitle(month)
100
+ : undefined
101
+ }
102
+ />
103
+ {isTwoUp ? (
104
+ <MonthCalendar
105
+ dateEnd={dateEnd}
106
+ dateStart={dateStart}
107
+ month={month + 1}
108
+ title={
109
+ useMonthAbbreviations
110
+ ? getAbbreviatedMonthTitle(month + 1)
111
+ : undefined
112
+ }
113
+ />
114
+ ) : null}
115
+ </div>
116
+ </div>
117
+ </Fragment>
118
+ );
119
+ }
@@ -2,21 +2,32 @@ import { Style } from '@acusti/styling';
2
2
  import clsx from 'clsx';
3
3
  import * as React from 'react';
4
4
 
5
- import { getMonthNameFromMonth, getYearFromMonth } from './utils.js';
5
+ import { getMonthFromDate, getMonthNameFromMonth, getYearFromMonth } from './utils.js';
6
6
  import { ROOT_CLASS_NAME, STYLES } from './styles/month-calendar.js';
7
7
 
8
8
  export type Props = {
9
9
  className?: string;
10
+ dateStart?: Date | string | number;
11
+ dateEnd?: Date | string | number;
10
12
  month: number; // a unique numerical value representing the number of months since jan 1970
11
13
  onChange?: (event: React.SyntheticEvent<HTMLElement>) => void;
12
14
  title?: string;
13
15
  };
14
16
 
17
+ type DateRangeDays = [number | null, number | null];
18
+
15
19
  const { Fragment, useCallback } = React;
16
20
 
17
21
  const DAYS = Array(7).fill(null);
18
22
 
19
- export default function MonthCalendar({ className, month, onChange, title }: Props) {
23
+ export default function MonthCalendar({
24
+ className,
25
+ dateEnd,
26
+ dateStart,
27
+ month,
28
+ onChange,
29
+ title,
30
+ }: Props) {
20
31
  const year = getYearFromMonth(month);
21
32
  title = title || `${getMonthNameFromMonth(month)} ${year}`;
22
33
  const monthWithinYear = month % 12;
@@ -29,6 +40,35 @@ export default function MonthCalendar({ className, month, onChange, title }: Pro
29
40
  const spacesAfterLastDay = 7 - (lastDate.getDay() % 7); // prettier-ignore
30
41
  const daySpaces = totalDays + firstDay + spacesAfterLastDay;
31
42
 
43
+ const [dateRangeStartDay, dateRangeEndDay]: DateRangeDays = [
44
+ dateStart,
45
+ dateEnd,
46
+ ].reduce(
47
+ (acc: DateRangeDays, date, index) => {
48
+ if (date != null && !(date instanceof Date)) {
49
+ date = new Date(date);
50
+ }
51
+ if (date == null || Number.isNaN(date.getTime())) return acc;
52
+
53
+ const dateMonth = getMonthFromDate(date);
54
+ if (dateMonth < month) acc[index] = -1;
55
+ else if (dateMonth > month) acc[index] = totalDays + 1;
56
+ else acc[index] = date.getDate();
57
+ if (index === 1) {
58
+ const startDay = acc[index - 1];
59
+ const endDay = acc[index];
60
+ // Ensure that end date is after start date and swap them if not
61
+ if (startDay != null && endDay != null && startDay > endDay) {
62
+ acc[index - 1] = endDay;
63
+ acc[index] = startDay;
64
+ }
65
+ }
66
+
67
+ return acc;
68
+ },
69
+ [null, null],
70
+ );
71
+
32
72
  const handleClickDay = useCallback(
33
73
  (event: React.SyntheticEvent<HTMLElement>) => {
34
74
  if (onChange) onChange(event);
@@ -67,7 +107,7 @@ export default function MonthCalendar({ className, month, onChange, title }: Pro
67
107
  </div>
68
108
  </div>
69
109
  <div className={`${ROOT_CLASS_NAME}-month-days`}>
70
- {Array(Math.ceil(daySpaces / 7))
110
+ {Array(Math.floor(daySpaces / 7))
71
111
  .fill(null)
72
112
  .map((_, weekIndex) => (
73
113
  <div
@@ -77,17 +117,41 @@ export default function MonthCalendar({ className, month, onChange, title }: Pro
77
117
  {DAYS.map((_, dayIndex) => {
78
118
  dayIndex += weekIndex * 7;
79
119
  const dayNumber = (dayIndex - firstDay) + 1; // prettier-ignore
120
+ const isEmpty =
121
+ dayNumber < 1 || dayNumber > totalDays;
122
+ const isAfterDateRangeStart =
123
+ dateRangeStartDay != null &&
124
+ dayNumber > dateRangeStartDay;
125
+ const isBeforeDateRangeEnd =
126
+ dateRangeEndDay != null &&
127
+ dayNumber < dateRangeEndDay;
128
+
80
129
  return (
81
130
  <div
82
- className={`${ROOT_CLASS_NAME}-month-day-item`}
131
+ className={clsx(
132
+ `${ROOT_CLASS_NAME}-month-day-item`,
133
+ {
134
+ 'is-empty': isEmpty,
135
+ 'is-selected':
136
+ !isEmpty &&
137
+ isAfterDateRangeStart &&
138
+ isBeforeDateRangeEnd,
139
+ 'end-date':
140
+ !isEmpty &&
141
+ dayNumber === dateRangeEndDay,
142
+ 'start-date':
143
+ !isEmpty &&
144
+ dayNumber === dateRangeStartDay,
145
+ },
146
+ )}
83
147
  key={`MonthDayItem-${dayNumber}`}
84
148
  onClick={handleClickDay}
85
149
  >
86
- <span className="month-day-item-text">
87
- {dayNumber < 1 || dayNumber > totalDays
88
- ? ''
89
- : dayNumber}
90
- </span>
150
+ {isEmpty ? null : (
151
+ <span className="month-day-item-text">
152
+ {dayNumber}
153
+ </span>
154
+ )}
91
155
  </div>
92
156
  );
93
157
  })}
@@ -0,0 +1,62 @@
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
+ `;
@@ -77,7 +77,7 @@ h3.${ROOT_CLASS_NAME}-month-title-text {
77
77
  cursor: pointer;
78
78
  }
79
79
 
80
- .${ROOT_CLASS_NAME}-month-day-item.selected {
80
+ .${ROOT_CLASS_NAME}-month-day-item.is-selected {
81
81
  background-color: #f8f8f8;
82
82
  }
83
83
 
@@ -132,6 +132,10 @@ h3.${ROOT_CLASS_NAME}-month-title-text {
132
132
  visibility: hidden;
133
133
  }
134
134
 
135
+ .${ROOT_CLASS_NAME}-month-day-item.is-empty:after {
136
+ content: none;
137
+ }
138
+
135
139
  .${ROOT_CLASS_NAME}-month-day-item span.month-day-item-text {
136
140
  text-align: center;
137
141
  font-size: 13px;
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { getMonthFromDate, getMonthNameFromMonth } from './utils.js';
4
+
5
+ describe('@acusti/date-picker', () => {
6
+ describe('utils', () => {
7
+ describe('getMonthNameFromMonth', () => {
8
+ it('returns the correct month name for a post-unix epoch month', () => {
9
+ expect(
10
+ getMonthNameFromMonth(getMonthFromDate(new Date(2023, 5, 19))),
11
+ ).toBe('June');
12
+ });
13
+
14
+ it('returns the correct month name for a pre-unix epoch month', () => {
15
+ expect(
16
+ getMonthNameFromMonth(getMonthFromDate(new Date(1865, 5, 19))),
17
+ ).toBe('June');
18
+ });
19
+
20
+ it('returns an empty string if given NaN (e.g. if dealing with an Invalid Date)', () => {
21
+ expect(getMonthNameFromMonth(getMonthFromDate(new Date('')))).toBe('');
22
+ });
23
+ });
24
+ });
25
+ });
package/src/utils.ts CHANGED
@@ -23,7 +23,12 @@ export const getMonthFromDate = (date: Date) =>
23
23
 
24
24
  export const getYearFromMonth = (month: number) => Math.floor(month / 12) + START_YEAR;
25
25
 
26
- export const getMonthNameFromMonth = (month: number) => MONTH_NAMES[month % 12];
26
+ export const getMonthNameFromMonth = (month: number): string => {
27
+ let index = month % 12;
28
+ if (Number.isNaN(index)) return '';
29
+ if (index < 0) index = 12 + index;
30
+ return MONTH_NAMES[index];
31
+ };
27
32
 
28
33
  export const getMonthAbbreviationFromMonth = (month: number) => {
29
34
  const monthName = getMonthNameFromMonth(month);