续小程序的日历,出一个react+ts版本的日历
实现方式
- 列表计算
- hooks切换(月,日)
- jsx页面渲染
三个独立的互相结合。 基于前面的hooks函数。页面可以自由选择他们想要的样式。而不是使用UI组件一样去基于别人的组件去修改样式,或者很难满足需求。
Preview
Code
dayListTool.ts
const _mothZh = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'].map(e => e + '月')
const _mothEn = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
export const mothsText = true ? _mothEn : _mothZh
// eslint-disable-next-line
const getMaxY = (y: number) => Boolean(y % 4 === 0 && y % 100 !== 0 || y % 400 === 0)
const getAugNum = (n: number) => getMaxY(n) ? 29 : 28
const getMothNum = (y: number) => ([31, getAugNum(y), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31])
export const getMothList = (year: number, month: number) => {
var star = new Date(Date.UTC(year, month - 1, 1)).getDay()
let mn = getMothNum(year)[month - 1]
var row: { date: number; number: number }[] = []
var res: (typeof row)[] = []
new Array(6 * 7)
.fill('')
.map((_, i) => i - star + 1)
.map(e =>
(e > 0 && e <= mn)
? ({
date: new Date(year, month - 1, e).getTime(),
number: e
})
: (null)
)
.forEach((item, i) => {
row.push(JSON.parse(JSON.stringify(item)))
if ((i + 1) % 7 === 0) {
if (row.map(e => e || '').join('') !== '') {
res.push(row)
}
row = []
}
})
return res
}
useDayList.ts
import { useMemo, useState } from "react"
import { getMothList, mothsText } from "../dayListTool"
const today = new Date()
export const useDayList = () => {
const [date, setDate] = useState(today)
const [month, setMonth] = useState(date.getMonth() + 1)
const [year, setYear] = useState(date.getFullYear())
const list = useMemo(() => getMothList(year, month), [month, year])
const onUpdateMoth = (_month: number, _year: number) => {
if(_month > 0 && _month <= 12){
setMonth(_month)
setYear(_year)
return
}
if(_month < 1){
setMonth(12)
setYear(_year - 1)
return
}
setMonth(1)
setYear(_year + 1)
}
const onUpMoth = () => onUpdateMoth(month + 1, year)
const onDownMoth = () => onUpdateMoth(month - 1, year)
return {
list,
year,
month,
onUpMoth,
onDownMoth,
mothsText,
date,
setDate,
// other
onUpdateMoth,
}
}
DayList.tsx
import { Box, Typography } from "@material-ui/core"
import { SVGProps, useState } from "react"
import { useDayList } from "./hooks/useDayList"
const formatDate = (v: Date) => {
return `${v.getFullYear()}-${v.getMonth() + 1}-${v.getDate()}`
}
export const DayListSelect: React.FC<{
onChange: (day: Date) => void;
}> = ({ onChange }) => {
const { list, year, month, mothsText, onUpMoth, onDownMoth, date, setDate } = useDayList()
const selectDay = (day: number) => {
setDate(new Date(day))
onChange(new Date(day))
}
const addDay = (v: number) => {
setDate(new Date(date.setDate(date.getDate() + v)))
}
const [isOpen, setIsOpen] = useState(false)
return <>
<Box width={800} display="flex" justifyContent="space-between" alignContent="center" style={{ border: '1px solid' }} onClick={() => setIsOpen(v => !v)}>
<span onClick={() => {
setIsOpen(false)
addDay(-1)
}}>left day</span>
<Typography>{formatDate(date)}</Typography>
<span onClick={() => {
setIsOpen(false)
addDay(1)
}
}>right day</span>
</Box>
{isOpen && <Box style={{ width: 800 }}>
<Box display="flex" justifyContent="space-between" alignContent="center">
<ArrowLeftIcon onClick={onDownMoth} />
<Typography>{mothsText[month - 1]} {year}</Typography>
<ArrowRightIcon onClick={onUpMoth} />
</Box>
<div>
{'SMTWTFS'.split('').map((e, key) => {
return <Box key={key} style={{ display: 'inline-block', width: 100 }}>{e}</Box>
})}
</div>
<div style={{ display: 'grid' }}>{list.map((row) => {
return <div>
{row.map((item, key) => {
const style = { display: 'inline-block', width: 100 }
if (!item) {
return <div key={key} style={{ ...style, opacity: 0 }}>x</div>
}
const isToday = formatDate(new Date(item.date)) === formatDate(new Date())
return <Box key={key} onClick={() => selectDay(item.date)} style={style}>
<Box style={{ ...(isToday ? { border: '1px solid ' } : {}) }}>{item?.number}</Box>
</Box>
})}
</div>
})}</div>
</Box>}
</>
}
const ArrowLeftIcon: React.FC<SVGProps<SVGSVGElement>> = (props) => {
return <svg width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M6.66797 1.66797L1.33463 7.0013L6.66797 12.3346" stroke="#3F3F3F" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}
const ArrowRightIcon: React.FC<SVGProps<SVGSVGElement>> = (props) => {
return <svg width="8" height="14" viewBox="0 0 8 14" fill="none" xmlns="http://www.w3.org/2000/svg"{...props}>
<path d="M1.33203 1.66797L6.66537 7.0013L1.33203 12.3346" stroke="#3F3F3F" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}