)
}
\ No newline at end of file
diff --git a/src/components/SingleDatePicker.tsx b/src/components/SingleDatePicker.tsx
index ef1582e..0d24d4e 100644
--- a/src/components/SingleDatePicker.tsx
+++ b/src/components/SingleDatePicker.tsx
@@ -3,20 +3,19 @@ import { getCalendarDates, getL10Weekday, generateUniqueId, applyColor } from '.
export interface SingleDatePickerProps {
/**
- * Control the selected
+ * Control the selected
* date programmatically, including situations like provide a default value or control the selected
* date by parent component. Use 1-12 for month, instead of 0-11, if you are using object to set the
* value.
* @example { year: 2025, month: 1, day: 1 }
* @example new Date(2025, 0, 1)
* @default new Date()
- */
+ */
value?: Date | { year: number, month: number, day: number }
/**
* A callback function that will be called when a date is selected inside the panel.
- * @param date - The date user selected.
- * @returns {{ year: number, month: number, day: number }} - The date user selected.
+ * @param {{ year: number, month: number, day: number }} - The date user selected.
* @example { year: 2025, month: 1, day: 1 } // User selected 1 Jan 2025
*/
onSelect?: (date: {
@@ -74,6 +73,7 @@ export interface SingleDatePickerProps {
}
/**
+ * SingleDatePicker
* A panel that allows users to select a date.
*
* @component
@@ -97,11 +97,11 @@ const SingleDatePicker: React.FC = ({ value, onSelect, lo
if (!value) return
if (!(value instanceof Date)) {
if (value.year < 100) value.year = Number(`20${value.year}`)
- if (value.month < 0 || value.month > 11)
+ if (value.month < 0 || value.month > 11)
return console.warn('Invalid value: Month should be between 1 and 12.')
if (value.day < 1 || value.day > 31)
return console.warn('Invalid value: Day should be between 1 and 31.')
- }
+ }
const date = value instanceof Date ? value : new Date(value.year, value.month - 1, value.day)
setSelectedDate(date)
setCurrentMonth(date.getMonth())
@@ -121,17 +121,7 @@ const SingleDatePicker: React.FC = ({ value, onSelect, lo
hoverColor: hoverColor,
borderColor: borderColor
})
- } , [mainColor, accentColor, reversedColor, hoverColor, borderColor])
-
- useEffect(() => {
- applyColor(uniqueId, {
- mainColor: mainColor,
- accentColor: accentColor,
- reversedColor: reversedColor,
- hoverColor: hoverColor,
- borderColor: borderColor
- })
- }, [])
+ }, [mainColor, accentColor, reversedColor, hoverColor, borderColor])
function selectDate(date: Date) {
setSelectedDate(date)
@@ -185,15 +175,17 @@ const SingleDatePicker: React.FC = ({ value, onSelect, lo
setCurrentYear(currentYear + 1)
}} aria-label={`Go to next year, ${currentYear + 1}, you are now at year ${currentYear}`}>
-
)
}
diff --git a/src/components/SingleWeekPicker.tsx b/src/components/SingleWeekPicker.tsx
new file mode 100644
index 0000000..95fc141
--- /dev/null
+++ b/src/components/SingleWeekPicker.tsx
@@ -0,0 +1,235 @@
+import { useEffect, useState } from "react"
+import { generateUniqueId, applyColor, getL10Weekday, getCalendarDates, calculateWeekNum } from "../utils"
+
+export interface SingleWeekPickerProps {
+ /**
+ * The language code that will be used to localize the panel.
+ * Accept standard ISO 639-1 language code, such as 'zh-CN', 'en-US', 'ja-JP', etc. Note
+ * that it will not effect to the screen reader, but the screen reader will still read the
+ * date in the user’s language.
+ * @default navigator.language
+ */
+ localization?: string
+
+ /**
+ * The main color of the panel, including the text color and the border color.
+ *@default '#000000'
+ */
+ mainColor?: string
+
+ /**
+ * The accent color of the panel, including the background color of the selected date.
+ *@default '#000000'
+ */
+ accentColor?: string
+
+ /**
+ * The reversed color of the panel, including the text color of the selected date.
+ *@default '#ffffff'
+ */
+ reversedColor?: string
+
+ /**
+ * The hover color of the panel, including the hover background color of the date.
+ *@default '#00000017'
+ */
+ hoverColor?: string
+
+ /**
+ * The border color of the panel, including the divider color between the header and the body.
+ *@default '#e0e0e0'
+ */
+ borderColor?: string
+
+ /**
+ * A callback function that will be called when a week is selected inside the panel. Note that
+ * Datenel will follow the ISO 8601 standard to calculate the week number, which means that the first
+ * week of the year is the week with the first Friday in it (week started from Monday).
+ * @param {{ year: number, month: number, day: number }} - The date user selected.
+ * @example { year: 2025, month: 1, day: 1 } // User selected 1 Jan 2025
+ */
+ onSelect?: (date: {
+ weekYear: number,
+ weekNum: number
+ }) => void
+
+ /**
+ * User requires to close the panel without select a specific date. Note that the close button is not
+ * visible, but can be read by screen reader. The close button for the screen reader is only available
+ * when this prop is not `undefined`.
+ * @default undefined
+ */
+ onClose?: () => void
+
+ /**
+ * Control the selected
+ * date programmatically, including situations like provide a default value or control the selected
+ * date by parent component. When using the Date object, the week number related to the date will be
+ * applied to the panel.
+ * @example { weekYear: 2025, weekNum: 1 }
+ * @example new Date(2025, 0, 1)
+ * @default new Date()
+ */
+ value?: { weekYear: number, weekNum: number } | Date
+}
+
+/**
+ * SingleWeekPicker
+ * A panel that allows users to select a week.
+ *
+ * @component
+ *
+ * @param
+ */
+export default ({ localization, mainColor = '#000000', accentColor = '#000000', reversedColor = '#ffffff', hoverColor = '#00000017', borderColor = '#e0e0e0', onClose, onSelect, value }: SingleWeekPickerProps) => {
+ const [currentMonth, setCurrentMonth] = useState(new Date().getMonth())
+ const [currentYear, setCurrentYear] = useState(new Date().getFullYear())
+ const [selectedWeek, setSelectedWeek] = useState<{ weekYear: number, weekNum: number }>(calculateWeekNum(new Date()))
+ const [selectMonth, setSelectMonth] = useState(false)
+ const [calendarWeeks, setCalendarWeeks] = useState([])
+ const [l10nDays, setL10nDays] = useState([])
+ const uniqueId = generateUniqueId()
+
+ function skipToLastMonth() {
+ if (currentMonth === 0) {
+ setCurrentMonth(11)
+ setCurrentYear(currentYear - 1)
+ }
+ else setCurrentMonth(currentMonth - 1)
+ }
+
+ function skipToNextMonth() {
+ if (currentMonth === 11) {
+ setCurrentMonth(0)
+ setCurrentYear(currentYear + 1)
+ }
+ else setCurrentMonth(currentMonth + 1)
+ }
+
+ useEffect(() => {
+ applyColor(uniqueId, {
+ mainColor: mainColor,
+ accentColor: accentColor,
+ reversedColor: reversedColor,
+ hoverColor: hoverColor,
+ borderColor: borderColor
+ })
+ }, [mainColor, accentColor, reversedColor, hoverColor, borderColor])
+
+ useEffect(() => {
+ const i18n = localization || navigator.language
+ setL10nDays(getL10Weekday(i18n))
+ }, [localization])
+
+ useEffect(() => {
+ const dates = getCalendarDates(currentMonth, currentYear)
+ let weeks: Date[][] = []
+ for (let i = 0; i < dates.length; i += 7)
+ weeks.push(dates.slice(i, i + 7))
+ setCalendarWeeks(weeks)
+ }, [currentMonth, currentYear])
+
+ useEffect(() => {
+ if (!value) return
+ if (!(value instanceof Date)) {
+ if (value.weekYear < 100) value.weekYear = Number(`20${value.weekYear}`)
+ if (value.weekNum < 1 || value.weekNum > 53)
+ return console.warn('Invalid value: Week number should be between 1 and 53.')
+ }
+ const date = value instanceof Date ? value : new Date(value.weekYear, 0, 1)
+ if ('weekNum' in value)
+ date.setDate(date.getDate() + (value.weekNum - 1) * 7)
+ setSelectedWeek(calculateWeekNum(date))
+ setCurrentMonth(date.getMonth())
+ setCurrentYear(date.getFullYear())
+ }, [value])
+
+ function selectWeek(date: Date) {
+ setSelectedWeek(calculateWeekNum(date))
+ onSelect?.(calculateWeekNum(date))
+ }
+
+ function changeYear(year: string) {
+ if (isNaN(Number(year))) return
+ if (Number(year) < 0) return
+ setCurrentYear(Number(year))
+ }
+
+ function adjustYear() {
+ if (currentYear < 100) setCurrentYear(Number(`20${currentYear}`))
+ }
+
+ if (selectMonth) {
+ return
+
+
+ changeYear(e.target.value)}
+ onBlur={adjustYear}
+ aria-label="Year input, type a year to go to that year"
+ />
+
+