React - 自己写一个日历📅

523 阅读2分钟

续小程序的日历,出一个react+ts版本的日历

小程序-日历签到

实现方式

  • 列表计算
  • hooks切换(月,日)
  • jsx页面渲染

三个独立的互相结合。 基于前面的hooks函数。页面可以自由选择他们想要的样式。而不是使用UI组件一样去基于别人的组件去修改样式,或者很难满足需求。

Preview

image.png

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>
}