feat: add SingleWeekPicker component and integrate with SingleDatePicker in the app

This commit is contained in:
Astrian Zheng 2025-02-20 16:36:13 +11:00
parent f4df007fa1
commit 768d68951b
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
7 changed files with 204 additions and 39 deletions

View File

@ -10,8 +10,13 @@ body {
justify-content: center;
align-items: center;
.border {
background: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 0.5rem;
> * {
background: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
}
}

View File

@ -1,5 +1,5 @@
import React from 'react'
import { SingleDatePicker } from "../src/index"
import { SingleDatePicker, SingleWeekPicker } from "../src/index"
import './app.scss'
export default () => {
@ -9,18 +9,9 @@ export default () => {
}
return (<div className='app'>
<div className="border">
<SingleDatePicker
value={{
year: 2025,
month: 1,
day: 1
}}
onSelect={onSelect}
localization="zh-CN"
onClose={() => alert('close')}
/>
<SingleWeekPicker />
<SingleDatePicker />
</div>
</div>)
}

View File

@ -0,0 +1,135 @@
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 users 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
}
/**
* 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' }: SingleWeekPickerProps) => {
const [currentMonth, setCurrentMonth] = useState(new Date().getMonth())
const [currentYear, setCurrentYear] = useState(new Date().getFullYear())
const [selectMonth, setSelectMonth] = useState(false)
const [calendarWeeks, setCalendarWeeks] = useState<Date[][]>([])
const [l10nDays, setL10nDays] = useState<string[]>([])
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(() => {
const date = new Date(2033, 0, 1)
console.log(date)
console.log(calculateWeekNum(date))
}, [])
if (selectMonth) {
return <div className='datenel-component' role="dialog" aria-label="Week selection panel, you are now at month and year quick-select" id={`__datenel-${uniqueId}`}></div>
} else {
return <div className='datenel-component' role="dialog" aria-label="Week selection panel" id={`__datenel-${uniqueId}`}>
<div className='header'>
<button className='stepper' onClick={skipToLastMonth} aria-label={`Go to last month, ${new Date(currentYear, currentMonth - 1).toLocaleString(localization || navigator.language, { month: 'long' })}`}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.8284 12.0007L15.7782 16.9504L14.364 18.3646L8 12.0007L14.364 5.63672L15.7782 7.05093L10.8284 12.0007Z"></path></svg></button>
<button className='indicator' onClick={() => setSelectMonth(true)} aria-label={`You are now at ${new Date(currentYear, currentMonth).toLocaleString(localization || navigator.language, { month: 'long', year: 'numeric' })}. Click here to quick-select month or year.`}>
{new Date(currentYear, currentMonth).toLocaleString(localization || navigator.language, { month: 'long', year: 'numeric' })}
</button>
<button className='stepper' onClick={skipToNextMonth} aria-label={`Go to next month, ${new Date(currentYear, currentMonth + 1).toLocaleString(localization || navigator.language, { month: 'long' })}`}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"></path></svg></button>
</div>
<div className='calendar-view-body flex' aria-live="polite">
<div className="listitem">
{Array.from({ length: 7 }).map((_, index) => <div className='item day-indicator' key={index}>{l10nDays[index]}</div>)}
</div>
{calendarWeeks.map((week, index) => <button className="listitem" key={index} onClick={() => console.log(calculateWeekNum(week[0]))}>
{week.map(date => <div
className={`item date ${currentMonth !== date.getMonth() && 'extra-month'}`}
key={date.getDate()}
>
{date.getDate()}
</div>)}
</button>)}
</div>
</div>
}
}

View File

@ -1,3 +1,4 @@
import './style.scss'
export {default as SingleDatePicker} from './components/SingleDatePicker'
export {default as SingleDatePicker} from './components/SingleDatePicker'
export {default as SingleWeekPicker} from './components/SingleWeekPicker'

View File

@ -71,6 +71,38 @@
&.grid{
display: grid;
grid-template-columns: repeat(7, 1fr);
.item.date {
border-radius: 50%;
position: relative;
&.extra-month {
cursor: default;
}
.today-indicator {
position: absolute;
bottom: 0.25rem;
width: 0.25rem;
height: 0.25rem;
}
&.active {
background: var(--datenel-accent-color);
color: var(--datenel-reversed-color);
}
}
}
&.flex {
display: flex;
flex-direction: column;
.listitem {
padding: 0;
display: grid;
grid-template-columns: repeat(7, 1fr);
border-radius: 1rem;
.item.date {
background: none;
}
}
}
.item {
width: 2rem;
@ -79,33 +111,15 @@
justify-content: center;
align-items: center;
font-size: 0.75rem;
}
.item.day-indicator {
opacity: 0.5;
}
button.item.date {
border-radius: 50%;
position: relative;
&.extra-month {
opacity: 0.3;
cursor: default;
}
&:hover {
background: var(--datenel-hover-color);
}
.today-indicator {
position: absolute;
bottom: 0.25rem;
width: 0.25rem;
height: 0.25rem;
}
&.active {
background: var(--datenel-accent-color);
color: var(--datenel-reversed-color);
&.day-indicator {
opacity: 0.5;
}
}
}
.month-selector-body {

View File

@ -0,0 +1,18 @@
export default (date: Date): { weekYear: number, weekNum: number } => {
const tempDate = new Date(date)
tempDate.setHours(0, 0, 0, 0)
tempDate.setDate(tempDate.getDate() + 3 - (tempDate.getDay() || 7))
const firstThursday = new Date(tempDate.getFullYear(), 0, 4)
firstThursday.setDate(firstThursday.getDate() + 3 - (firstThursday.getDay() || 7))
const diffInDays = Math.floor((tempDate.getTime() - firstThursday.getTime()) / (24 * 60 * 60 * 1000))
const weekNum = Math.ceil((diffInDays + 1) / 7)
return {
weekYear: weekNum === 53 ? tempDate.getFullYear() + 1 : tempDate.getFullYear(),
weekNum
}
}

View File

@ -1,4 +1,5 @@
export { default as getCalendarDates } from './getCalendarDates'
export { default as getL10Weekday } from './getL10Weekday'
export { default as generateUniqueId } from './generateUniqueId'
export { default as applyColor } from './applyColor'
export { default as applyColor } from './applyColor'
export { default as calculateWeekNum } from './calculateWeekNum'