1.0.2 • Published 4 years ago

u-calendar v1.0.2

Weekly downloads
1
License
MIT
Repository
github
Last release
4 years ago

u-calendar

Create calendar class only

useage

first step:

npm install u-calendar --save

Second step:

import UCalendar from "u-calendar";

const calendar = new UCalendar(options);

examples

react

Calendar.tsx

import React, { useState, useEffect, memo, forwardRef, useImperativeHandle } from 'react';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { Checkbox } from 'antd';
import { CheckboxProps } from 'antd/es/checkbox';
import classnames from 'classnames';
import UCalendar from 'u-calendar';
import {
  CalendarInstance,
  CalendarProps,
  WeekCell,
  DateType,
  DateCell,
  DayType,
} from 'u-calendar/src/data.d';
import { getDateTimestamp, dateFomater } from 'u-calendar/src/utils';
import styles from './index.less';

interface ICalendarProps extends CalendarProps {
  value?: DateType;
  className?: string;
  from?: CalendarInstance;
  toolbarActions?: React.ReactNode | React.ReactNode[];
  onDateChange?: (payload: { year: string; month: string; dates: DateCell[] }) => void;
  onChange?: (value?: DateType) => void;
  onCellClick?: (cell: DateCell, index: number) => void;
  showMonthCheckbox?: boolean;
  showWeekCheckbox?: boolean;
  toolbarRender?: (currentYear: string, currentMonth: string) => React.ReactNode;
  headerRender?: (payload: WeekCell, index: number) => React.ReactNode;
  cellRender?: (cell: DateCell, index: number, rows: DateCell[]) => React.ReactNode;
}

function useCalendar(from?: CalendarInstance, optons?: CalendarProps): [CalendarInstance] {
  const formRef = React.useRef<CalendarInstance>();

  if (!formRef.current) {
    if (from) {
      formRef.current = from;
    } else {
      const calendar = new UCalendar(optons || {});

      formRef.current = calendar.getCalendar();
    }
  }

  return [formRef.current];
}

const SpanCell: React.FC<CheckboxProps> = ({ children }) => <span>{children}</span>;

const Calendar: React.ForwardRefRenderFunction<any, ICalendarProps> = (props, ref: any) => {
  const {
    className,
    from,
    toolbarActions,
    toolbarRender,
    headerRender,
    cellRender,
    showMonthCheckbox,
    showWeekCheckbox,
    onCellClick,
    onChange,
    onDateChange,
    ...rest
  } = props;
  const [checkedValues, setCheckedValues] = useState<number[]>([]);
  const [dates, setDates] = useState<DateCell[]>([]);
  const [currentYear, setCurrentYear] = useState<string>('');
  const [currentMonth, setCurrentMonth] = useState<string>('');
  const [calendar] = useCalendar(from, rest);
  const { weekCells, dateCells, checkeds, currentYear: curYear, currentMonth: curMonth } = calendar;

  useImperativeHandle(ref, () => ({
    clearChecked: () => {
      calendar.clearChecked();
    },
  }));
  const ToolbarCheckbox = showMonthCheckbox ? Checkbox : SpanCell;
  const HeaderCheckbox = showWeekCheckbox ? Checkbox : SpanCell;

  const isAllChecked = (day?: DayType) => {
    const items = dates
      .filter((v) => !v.disabled && v.inView && (day === undefined || day === v.day))
      .map((v) => v.timestamp);
    if (items.length && items.every((v) => checkedValues.includes(v))) {
      return true;
    }
    return false;
  };

  const isEqualArray = (arr: number[], newArr: number[]): boolean => {
    if (arr.length === newArr.length && arr.every((v, i) => v === newArr[i])) {
      return true;
    }
    return false;
  };

  useEffect(() => {
    setDates([...dateCells]);
    setCurrentYear(curYear);
    setCurrentMonth(curMonth);
    setCheckedValues([...checkeds]);
  }, []);

  useEffect(() => {
    if (rest.value === undefined) {
      calendar.clearChecked();
    }
    if (rest.value) {
      calendar.clearChecked();
      const vals = calendar.setChecked(rest.value, true);
      if (!isEqualArray(checkedValues, vals)) {
        setCheckedValues([...vals]);
      }
    }
  }, [rest.value]);

  useEffect(() => {
    calendar.on('change', (data) => {
      setCheckedValues([...data]);
      if (typeof onChange === 'function') {
        const vals = [...data].map((v) => dateFomater(v, rest.valueFormat || 'YYYY-MM-DD'));
        onChange(rest.maxChecked === 1 ? vals.join(',') : vals);
      }
    });
    return () => {
      calendar.off('change');
    };
  }, [onChange]);

  useEffect(() => {
    calendar.on('dateChange', ({ dateCells: datas, year, month }) => {
      setCurrentYear(year);
      setCurrentMonth(month);
      setDates([...datas]);
      if (typeof onDateChange === 'function') {
        onDateChange({ year, month, dates: datas });
      }
    });
    return () => {
      calendar.off('dateChange');
    };
  }, [onDateChange]);

  return (
    <div className={classnames([styles.calendar, className])} ref={ref}>
      <div className="calendar-toolbar">
        <LeftOutlined className="btn prev" onClick={() => calendar.prev()} />
        {toolbarRender ? (
          toolbarRender(currentYear, currentMonth)
        ) : (
          <div className="calendar-toolbar-con">
            {toolbarActions}
            <span>{currentYear}</span>
            <span>年</span>
            <span>{currentMonth}</span>
            <span>月</span>
            <ToolbarCheckbox
              checked={isAllChecked()}
              onChange={(e) => {
                const { checked } = e.target;
                calendar.setChecked(undefined, checked);
              }}
              style={{ marginLeft: 10 }}
            >
              {showMonthCheckbox ? '全月' : ''}
            </ToolbarCheckbox>
          </div>
        )}
        <RightOutlined className="btn next" onClick={() => calendar.next()} />
      </div>
      <ul className="calendar-header">
        {weekCells.map((item, index) => {
          return (
            <li key={item.value}>
              {headerRender ? (
                headerRender(item, index)
              ) : (
                <HeaderCheckbox
                  checked={isAllChecked(item.value)}
                  onChange={(e) => {
                    const { checked } = e.target;
                    calendar.setChecked(item.value, checked);
                  }}
                >
                  {item.name}
                </HeaderCheckbox>
              )}
            </li>
          );
        })}
      </ul>
      <ul className="calendar-body">
        {dates.map((v, i) => {
          return (
            <li
              className={classnames([
                'calendar-cell',
                {
                  'is-other': !v.inView,
                  'is-disabled': v.disabled,
                  'is-checked': checkedValues.includes(v.timestamp),
                  'is-today': v.timestamp === getDateTimestamp(new Date(), true),
                },
              ])}
              key={v.value}
              onClick={() => {
                if (v.disabled) {
                  return;
                }
                if (v.inView) {
                  calendar.setChecked(v.timestamp);
                }
                if (typeof onCellClick === 'function') {
                  onCellClick(v, i);
                }
              }}
            >
              <div className="calendar-cell-box">
                <div className="calendar-cell-date">{v.date}</div>
                <div className="calendar-cell-con">
                  {cellRender ? cellRender(v, i, dates) : null}
                </div>
              </div>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default memo(forwardRef(Calendar));

vue

Calendar.vue

<template name="calendar">
  <div :class="['calendar', className]">
    <ul class="calendar-hd">
      <li v-for="week in weekCells" :key="week.value" class="cell week-cell">{{ week.name }}</li>
    </ul>
    <ul class="calendar-bd">
      <li
        v-for="(item, index) in dates"
        :key="item.timestamp"
        :class="[
          'cell date-cell',
          {
            indiv: item.indiv,
            disabled: item.disabled,
            checked: checkeds.includes(item.timestamp)
          }
        ]"
        @tap="onCellClick(item, index)"
      >
        <slot :cell="item" :checked="checkeds.includes(item.timestamp)">
          <div>{{ item.date }}</div>
        </slot>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from "vue-property-decorator";
import UCalendar from 'u-calendar';
import { dateFomater } from "u-calendar/src/utils";
import {
  CalendarInstance,
  DateType,
  DateCell,
  WeekCell,
  DayType,
} from "u-calendar/src/data.d";

let calendar: CalendarInstance;

@Component
export default class Calendar extends Vue {
  @Prop() private className?: string;
  @Prop() private current?: DateType;
  @Prop() private value?: DateType | DateType[];
  @Prop() private defaultValue?: DateType | DateType[];
  @Prop() private maxChecked?: number;
  @Prop() private disabled?: boolean;
  @Prop({ default: "YYYY-MM-DD" }) private valueFormat!: string;
  @Prop() private startWeek?: DayType;
  @Prop() private disabledDate?: (currentDate: DateCell) => boolean;
  @Prop() private customCells?: PlainObject[];

  weekCells: WeekCell[] = [];
  dateCells: DateCell[] = [];
  checkeds: number[] = [];

  @Watch("current")
  onCurrentChange(nv: DateType) {
    if (calendar) {
      this.dateCells = calendar.setCurrent(nv);
    }
  }

  private get dates() {
    const arr = this.dateCells.map((v) => {
      const item = { ...v };
      const custom =
        this.customCells?.find((v) => v.value === item.value) || {};
      const cur = { ...item, ...custom };
      if (typeof this.disabledDate === "function") {
        cur.disabled = this.disabledDate(cur);
      }
      return cur;
    });
    return arr;
  }

  init() {
    const calendar = new UCalendar(this.$props || {}).getCalendar();

    this.weekCells = calendar.weekCells;
    this.dateCells = calendar.dateCells;
    this.checkeds = calendar.checkeds;

    calendar.on("change", () => {
      const vals = this.checkeds.map((v) => dateFomater(v, this.valueFormat));
      const val = this.maxChecked === 1 ? vals[0] : vals;
      this.$emit("update:value", val);
      this.$emit("change", val);
    });

    calendar.on("dateChange", (data) => {
      this.$emit("dateChange", data);
    });
  }

  @Emit("onCellTap")
  onCellClick(cell: DateCell, index: number) {
    if (cell.disabled || this.disabled) return;
    calendar.setChecked(
      cell.timestamp,
      this.maxChecked === 1 ? true : undefined
    );
    return { cell, index };
  }

  created() {
    this.init();
  }

  destroyed() {
    if (calendar) {
      calendar.off("change");
      calendar.off("dateChange");
    }
  }
}
</script>

<style lang="less">
@import "./index.less";
</style>