From 4b0b821da58cd4ed146aabe0538c22df0a6edcd8 Mon Sep 17 00:00:00 2001 From: Astrian Zheng Date: Fri, 21 Feb 2025 17:42:31 +1100 Subject: [PATCH 1/4] feat: add available date range feature to SingleDatePicker component --- playground/app.tsx | 10 ++- src/components/SingleDatePicker.tsx | 119 +++++++++++++++++++++++----- src/style.scss | 17 +++- 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/playground/app.tsx b/playground/app.tsx index 77c036a..59e02dd 100644 --- a/playground/app.tsx +++ b/playground/app.tsx @@ -6,7 +6,15 @@ export default () => { return (
- console.log(date)} /> +
) } \ No newline at end of file diff --git a/src/components/SingleDatePicker.tsx b/src/components/SingleDatePicker.tsx index 0d24d4e..241cd5c 100644 --- a/src/components/SingleDatePicker.tsx +++ b/src/components/SingleDatePicker.tsx @@ -70,6 +70,24 @@ export interface SingleDatePickerProps { *@default '#e0e0e0' */ borderColor?: string + + /** + * Limit a range of dates that can be selected. It should be an array of two dates, which the first + * one is the available range start date, and the second one is the available range end date. + * + * If the first one is null, it means that the all dates after the second one is not available. If the second + * one is null, it means that the all dates before the first one is not available. + * + * If the first one is behind the second one, Datenel will exchange them automatically. + * + * The parameter will be ignored if the array length is not 2. + * @example [new Date(2025, 0, 1), new Date(2025, 11, 31)] + * @example [new Date(2025, 0, 1), null] + * @example [null, new Date(2025, 11, 31)] + * @example [new Date(2025, 11, 31), new Date(2025, 0, 1)] + * @default undefined + */ + availableRange?: [(Date | { year: number, month: number, day: number } | null), (Date | { year: number, month: number, day: number } | null)] } /** @@ -80,7 +98,7 @@ export interface SingleDatePickerProps { * * @param {SingleDatePickerProps} props */ -const SingleDatePicker: React.FC = ({ value, onSelect, localization, onClose, mainColor = '#000000', accentColor = '#000000', reversedColor = '#ffffff', hoverColor = '#00000017', borderColor = '#e0e0e0' }: SingleDatePickerProps) => { +const SingleDatePicker: React.FC = ({ value, onSelect, localization, onClose, mainColor = '#000000', accentColor = '#000000', reversedColor = '#ffffff', hoverColor = '#00000017', borderColor = '#e0e0e0', availableRange: inputAvailableRange }: SingleDatePickerProps) => { const [currentMonth, setCurrentMonth] = useState(new Date().getMonth()) const [currentYear, setCurrentYear] = useState(new Date().getFullYear()) const [selectedDate, setSelectedDate] = useState(new Date()) @@ -88,6 +106,8 @@ const SingleDatePicker: React.FC = ({ value, onSelect, lo const [l10nDays, setL10nDays] = useState([]) const [selectMonth, setSelectMonth] = useState(false) const uniqueId = generateUniqueId() + const [availableRangeStart, setAvailableRangeStart] = useState(null) + const [availableRangeEnd, setAvailableRangeEnd] = useState(null) useEffect(() => { setDates(getCalendarDates(currentMonth, currentYear)) @@ -123,6 +143,46 @@ const SingleDatePicker: React.FC = ({ value, onSelect, lo }) }, [mainColor, accentColor, reversedColor, hoverColor, borderColor]) + useEffect(() => { + if (!inputAvailableRange) { + setAvailableRangeEnd(null) + setAvailableRangeStart(null) + return + } + if (inputAvailableRange.length !== 2) { + console.warn('Invalid availableRange: The length of the array should be 2. The parameter will be ignored.') + setAvailableRangeEnd(null) + setAvailableRangeStart(null) + return + } + const [start, end] = inputAvailableRange + if (start && end) { + const inputStart = !(start instanceof Date) ? new Date(start.year, start.month - 1, start.day) : start + const inputEnd = !(end instanceof Date) ? new Date(end.year, end.month - 1, end.day) : end + if (inputStart > inputEnd) { + setAvailableRangeStart(inputEnd) + setAvailableRangeEnd(inputStart) + } else { + setAvailableRangeStart(inputStart) + setAvailableRangeEnd(inputEnd) + } + } + else if (start && !end) { + if (!(start instanceof Date)) setAvailableRangeStart(new Date(start.year, start.month - 1, start.day)) + else setAvailableRangeStart(start) + setAvailableRangeEnd(null) + } + else if (!start && end) { + if (!(end instanceof Date)) setAvailableRangeEnd(new Date(end.year, end.month - 1, end.day)) + else setAvailableRangeEnd(end) + setAvailableRangeStart(null) + } + else { + setAvailableRangeStart(null) + setAvailableRangeEnd(null) + } + }, [inputAvailableRange]) + function selectDate(date: Date) { setSelectedDate(date) onSelect?.({ @@ -177,12 +237,30 @@ const SingleDatePicker: React.FC = ({ value, onSelect, lo
- {Array.from({ length: 12 }).map((_, index) => )} + {Array.from({ length: 12 }).map((_, index) => { + function calculateNotAvailable() { + // When the last day of a month not inside the range of available dates + const lastDayOfMonth = new Date(currentYear, index + 1, 0) + if (availableRangeStart && lastDayOfMonth < availableRangeStart) return true + // When the first day of a month not inside the range of available dates + const firstDayOfMonth = new Date(currentYear, index, 1) + if (availableRangeEnd && firstDayOfMonth > availableRangeEnd) return true + return false + } + return + })}
{!!onClose && } @@ -201,18 +279,21 @@ const SingleDatePicker: React.FC = ({ value, onSelect, lo
{l10nDays.map((day, index) =>
{day}
)} - {dates.map(date => )} + {dates.map(date => { + const notAvailable = (availableRangeStart && date < availableRangeStart) || (availableRangeEnd && date > availableRangeEnd) || currentMonth !== date.getMonth() + return + })}
{!!onClose && } diff --git a/src/style.scss b/src/style.scss index 5ab182e..c3075f1 100644 --- a/src/style.scss +++ b/src/style.scss @@ -83,7 +83,7 @@ .item.date { border-radius: 50%; - &.extra-month { + &.extra-month, &.not-available { cursor: default; } @@ -112,7 +112,7 @@ background: var(--datenel-accent-color); color: var(--datenel-reversed-color); - .extra-month { + .extra-month, .not-available { opacity: 0.5; } } @@ -129,8 +129,11 @@ font-size: 0.75rem; position: relative; - &.extra-month { + &.extra-month, &.not-available { opacity: 0.3; + &:hover { + background: none; + }; } &.day-indicator { @@ -156,6 +159,14 @@ border-radius: 0.25rem; height: 2rem; padding: 0 0.5rem; + + &.not-available { + opacity: 0.3; + cursor: default; + &:hover { + background: none; + }; + } } } -- 2.45.1 From 46e1fefa69672aac0485d9f7face680168a56776 Mon Sep 17 00:00:00 2001 From: Astrian Zheng Date: Fri, 21 Feb 2025 17:45:13 +1100 Subject: [PATCH 2/4] fix: update available date range in SingleDatePicker component --- playground/app.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/playground/app.tsx b/playground/app.tsx index 59e02dd..ccab5c9 100644 --- a/playground/app.tsx +++ b/playground/app.tsx @@ -7,13 +7,13 @@ export default () => { return (
) -- 2.45.1 From a8abac8f49eb5e87bbc24139c9a8fbcbbd0d07b0 Mon Sep 17 00:00:00 2001 From: Astrian Zheng Date: Fri, 21 Feb 2025 17:50:03 +1100 Subject: [PATCH 3/4] 0.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7717c2a..3aea986 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "datenel-react", - "version": "0.0.2", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "datenel-react", - "version": "0.0.2", + "version": "0.1.0", "devDependencies": { "@eslint/js": "^9.19.0", "@types/node": "^22.13.4", diff --git a/package.json b/package.json index fded320..f7af0df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "datenel-react", - "version": "0.0.3", + "version": "0.1.0", "scripts": { "dev": "vite", "build": "vite build", -- 2.45.1 From aa7dc297aff267d8278da52df2b0564d55c909eb Mon Sep 17 00:00:00 2001 From: Astrian Zheng Date: Fri, 21 Feb 2025 17:53:57 +1100 Subject: [PATCH 4/4] chore: add repository information to package.json --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package.json b/package.json index f7af0df..d2068ab 100644 --- a/package.json +++ b/package.json @@ -40,5 +40,15 @@ "types": "dist/index.d.ts", "files": [ "dist" + ], + "repository": { + "type": "git", + "url": "https://github.com/Astrian/datenel-react" + }, + "additionalRepositories": [ + { + "type": "git", + "url": "https://git.nas.astrian.moe/Astrian/datenel-react" + } ] } -- 2.45.1