UNPKG

@amaui/ui-react

Version:
486 lines (483 loc) 18.5 kB
import _extends from "@babel/runtime/helpers/extends"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["date", "times", "events", "onOpen", "onTimeClick", "render", "statuses", "displayTime", "day", "className", "children"]; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } import React from 'react'; import { cleanValue, is, textToInnerHTML } from '@amaui/utils'; import { style as styleMethod, classNames, useAmauiTheme, colors } from '@amaui/style-react'; import { AmauiDate, add, endOf, format, set, startOf } from '@amaui/date'; import LineElement from '../Line'; import TypeElement from '../Type'; import TooltipElement from '../Tooltip'; import { formats, staticClassName } from '../utils'; const useStyle = styleMethod(theme => ({ root: { color: theme.methods.palette.color.value('primary', 10), background: theme.palette.background.default.primary }, weekDay: { width: '44px', height: '44px', borderRadius: '50%' }, today: { background: theme.palette.color.primary[40], color: '#fff' }, days: { overflow: 'auto hidden' }, day: { minWidth: '184px', height: '134vh', width: 'calc((100% / 7) - 106px)' }, dayBody: { position: 'relative', borderLeft: `1px solid ${theme.palette.light ? '#dadada' : '#575757'}` }, dayHeader: { height: '78px', padding: '4px' }, hours: { position: 'sticky', top: '0px', left: '0px', width: '44px', height: '134vh', flex: '0 0 auto', marginRight: '8px', background: theme.palette.background.default.primary, zIndex: 14 }, hourWrapper: { position: 'relative' }, hour: { width: '50px', alignSelf: 'flex-start', left: '0px', position: 'absolute', top: '0', marginTop: '-8px', zIndex: '40', borderRadius: theme.methods.shape.radius.value(0.5) }, guide: { position: 'absolute', left: '-9px', height: '1px', width: 'calc(100% + 9px)', background: theme.palette.light ? '#dadada' : '#575757' }, guidelineHour: { position: 'absolute', left: '0px', height: '2px', transform: 'translateY(-50%)', background: theme.palette.color.tertiary[50], zIndex: 14, '&::before': { position: 'absolute', content: "''", width: '12px', height: '12px', borderRadius: '50%', background: theme.palette.color.tertiary[50], top: '-5px', left: '-6.5px' } }, range: { position: 'absolute', left: 0, right: 0, cursor: 'pointer', zIndex: 1, transition: theme.methods.transitions.make('box-shadow'), overflow: 'hidden', '& > *': { whiteSpace: 'pre-wrap', transition: theme.methods.transitions.make('opacity') }, '& > .amaui-work-day-time': { opacity: 0 }, '&:hover': { zIndex: 14, boxShadow: theme.shadows.values.default[2], '& > .amaui-work-day-time': { opacity: 1 } } }, time: { position: 'relative', zIndex: 1, padding: '0px 2px', borderRadius: 2 } }), { name: 'amaui-CalendarWeek' }); const CalendarWeek = /*#__PURE__*/React.forwardRef((props_, ref) => { const theme = useAmauiTheme(); const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiCalendarWeek?.props?.default), props_), [props_]); const Line = React.useMemo(() => theme?.elements?.Line || LineElement, [theme]); const Type = React.useMemo(() => theme?.elements?.Type || TypeElement, [theme]); const Tooltip = React.useMemo(() => theme?.elements?.Tooltip || TooltipElement, [theme]); const { date, times: timesProps, events, onOpen, onTimeClick, render, statuses = {}, displayTime = true, day: dayProp, className, children } = props, other = _objectWithoutProperties(props, _excluded); const { classes } = useStyle(); const [now, setNow] = React.useState(new AmauiDate()); const refs = { date: React.useRef(date), displayTime: React.useRef(displayTime), interval: React.useRef(undefined), days: React.useRef({}), overlaping: React.useRef({}), statuses: React.useRef(statuses) }; refs.date.current = date; refs.displayTime.current = displayTime; refs.statuses.current = statuses; const times = React.useMemo(() => { if (events) { return [{ dates: { active: true, values: (is('array', events) ? events : [events]).filter(Boolean) } }]; } return (is('array', timesProps) ? timesProps : [timesProps]).filter(Boolean); }, [events, timesProps]); const rangeShade = theme.palette.light ? 60 : 40; React.useEffect(() => { // 1 minute refs.interval.current = setInterval(() => { setNow(new AmauiDate()); }, 60 * 1e3); return () => { clearInterval(refs.interval.current); }; }, []); const getDates = React.useCallback(available => { const values = available.dates?.values || []; return values.map(item => { if (item.entire) { if (item.from) { let from = new AmauiDate(item.from); let to; if (['day', 'week', 'month', 'year'].includes(item.entire)) from = startOf(from, 'day'); if (item.entire === 'minute') from = startOf(from, 'minute'); if (item.entire === 'hour') from = startOf(from, 'hour'); to = endOf(from, item.entire); item.from = from.milliseconds; item.to = to.milliseconds; } } return item; }); }, [times]); const getColor = React.useCallback(item => { let palette = theme.palette.color.neutral; if (item?.status === 'working') palette = theme.palette.color.success; if (item?.status === 'not-working') palette = theme.palette.color.info; if (item?.status === 'break') palette = theme.palette.color.warning; if (item?.status === 'pending') palette = theme.methods.color(colors.yellow[50]); if (item?.status === 'rescheduled') palette = theme.methods.color(colors.purple[50]); if (item?.status === 'canceled') palette = theme.palette.color.error; if (item?.status === 'other') palette = theme.palette.color.neutral; if (item.color) palette = theme.methods.color(item.color); return palette[rangeShade]; }, [rangeShade, colors, theme]); const itemToText = React.useCallback(item => { if (item === 'pending') return 'scheduled'; if (item === 'not-count-workout-session') return `don't count workout session`; return item; }, []); const renderTimes = function (day, valuesAll) { let weekly = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; let itemDay = arguments.length > 3 ? arguments[3] : undefined; if (itemDay !== undefined && !itemDay?.active) return null; if (weekly) { const ends_at = itemDay?.ends_at ? new AmauiDate(itemDay.ends_at) : undefined; if (ends_at) { const day_StartDay = startOf(day, 'day'); const ends_at_StartDay = startOf(ends_at, 'day'); if (day_StartDay.milliseconds >= ends_at_StartDay.milliseconds) return null; } } const dayStartOfDay = startOf(day, 'day'); const dayDate = format(day, formats.date); const values = valuesAll?.filter(item => { const from = new AmauiDate(item.from); const fromStartOfDay = startOf(from, 'day'); const to = new AmauiDate(item.to); const toStartOfDay = startOf(to, 'day'); return [undefined, true].includes(refs.statuses.current[item.status || 'working']) && (weekly ? weekly : // from (dayDate === format(from, formats.date) || dayStartOfDay.milliseconds > fromStartOfDay.milliseconds) && ( // to dayDate === format(to, formats.date) || dayStartOfDay.milliseconds < toStartOfDay.milliseconds)); }); const elements = []; const renderTo = valueNew => { if (valueNew === '23:59') return '24:00'; return valueNew; }; values?.forEach((item, index) => { if (!(item.from && item.to)) return; let from = new AmauiDate(item.from); let to = new AmauiDate(item.to); if (!weekly) { const fromStartOfDay = startOf(from, 'day'); const toStartOfDay = startOf(to, 'day'); if (dayStartOfDay?.milliseconds > fromStartOfDay?.milliseconds && (dayDate === format(to, formats.date) || dayStartOfDay.milliseconds < toStartOfDay.milliseconds)) item.from = startOf(from, 'day').milliseconds; if ((dayDate === format(from, formats.date) || dayStartOfDay.milliseconds > fromStartOfDay.milliseconds) && dayStartOfDay?.milliseconds < toStartOfDay?.milliseconds) item.to = endOf(to, 'day').milliseconds; } from = new AmauiDate(item.from); to = new AmauiDate(item.to); const itemDate = format(day, formats.date); const top = +(100 * ((from.hour * 60 + from.minute) / (24 * 60))).toFixed(4); const bottom = +(100 - 100 * ((to.hour * 60 + (to.minute === 59 ? 60 : to.minute)) / (24 * 60))).toFixed(4); if (!refs.days.current[itemDate]) refs.days.current[itemDate] = []; if (!refs.overlaping.current[itemDate]) refs.overlaping.current[itemDate] = []; const bottom_ = 100 - bottom; // intersections const overlaps = refs.days.current[itemDate].filter(_ref => { let [itemTop, itemBottom] = _ref; return !(top >= itemBottom || bottom_ <= itemTop); }); let level = 0; if (overlaps.length) { level = refs.overlaping.current[itemDate].filter(_ref2 => { let [itemTop, itemBottom] = _ref2; return !(top >= itemBottom || bottom_ <= itemTop); }).length + 1; refs.overlaping.current[itemDate].push([top, bottom_]); } refs.days.current[itemDate].push([top, bottom_]); const minimal = 100 - bottom - top < 5; const background = getColor(item); const WrapperElement = item.status ? Tooltip : React.Fragment; const WrapperElementProps = item.status ? { name: cleanValue(itemToText(item.status), { capitalize: true }), color: getColor(item) } : undefined; const itemProps = { onClicl: event => onOpen(_objectSpread(_objectSpread({}, item), {}, { day, weekly }), event), className: classes.range }; const style = _objectSpread({ top: `${top}%`, bottom: `${bottom}%`, color: theme.methods.palette.color.text(background), background, left: `calc(0px + ${level * 10}px)` }, top === 0 && bottom === 0 && { border: 'none' }); const elementRendered = is('function', render) ? render(item, dayProp ? 'day' : 'week') : /*#__PURE__*/React.createElement(Line, _extends({ key: index, gap: 0.5, align: "center", justify: "center" }, itemProps), !minimal && refs.displayTime.current && /*#__PURE__*/React.createElement(Type, { version: "l2", weight: 300, align: "center", className: classes.time, style: { background: getColor(item) } }, format(from, 'hh:mm a'), " - ", renderTo(format(to, 'hh:mm a'))), item.description && /*#__PURE__*/React.createElement(Type, { version: "b2", dangerouslySetInnerHTML: { __html: textToInnerHTML(item.description) }, style: { maxHeight: 24, maxWidth: '90%' } })); elements.push( /*#__PURE__*/React.createElement(WrapperElement, WrapperElementProps, /*#__PURE__*/React.cloneElement(elementRendered, _objectSpread(_objectSpread({}, itemProps), {}, { className: classNames([itemProps.className, elementRendered.props?.className]), style: _objectSpread(_objectSpread({}, style), elementRendered.props?.style) })))); }); return elements; }; const onTimeClickMethod = React.useCallback((itemDay, event) => { const rect = event.currentTarget.getBoundingClientRect(); const relativeY = event.clientY - rect.top; const relativePercentage = relativeY / rect.height * 100; let timeDate = set(Math.floor(24 * (relativePercentage / 100)), 'hour', itemDay); // start of the hour timeDate = startOf(timeDate, 'hour'); onTimeClick?.(timeDate, dayProp ? 'day' : 'week', event); }, []); const timesUI = React.useCallback(dayDate => { // clean up refs.days.current = {}; refs.overlaping.current = {}; const day = dayDate.dayWeek === 0 ? 7 : dayDate.dayWeek; return /*#__PURE__*/React.createElement(React.Fragment, null, times.map(item => /*#__PURE__*/React.createElement(React.Fragment, null, item.weekly && renderTimes(dayDate, item.weekly.days[day]?.values, true, item.weekly.days[day]), item.dates && renderTimes(dayDate, getDates(item), false)))); }, [theme, times, date]); const hours = React.useMemo(() => { return Array.from({ length: 24 }).map((item, index) => set(index, 'hour', date)); }, [date]); const days = React.useMemo(() => { const weekStartDate = set(4, 'hour', startOf(date, 'week')); return Array.from({ length: 7 }).map((_, index) => add(index, 'day', weekStartDate)); }, [date]); return /*#__PURE__*/React.createElement(Line, _extends({ ref: ref, gap: 1.5, flex: true, fullWidth: true, className: classNames([staticClassName('CalendarWeek', theme) && ['amaui-CalendarWeek-root', dayProp && 'amaui-CalendarWeek-prop-day'], className, classes.root]) }, other), /*#__PURE__*/React.createElement(Line, { gap: 0, direction: "row", align: "stretch", flex: true, fullWidth: true, className: classes.days }, /*#__PURE__*/React.createElement(Line, { gap: 0, align: "center", fullWidth: true, className: classNames(['amaui-hours', classes.hours]) }, /*#__PURE__*/React.createElement(Line, { className: classes.dayHeader }), /*#__PURE__*/React.createElement(Line, { gap: 0, flex: true, fullWidth: true, className: classes.hourWrapper }, hours.map((itemHour, index) => /*#__PURE__*/React.createElement(Line, { key: index, gap: 1, align: "unset", className: classes.hour, style: { top: `${100 / 24 * index}%` } }, /*#__PURE__*/React.createElement(Type, { version: "b3", whiteSpace: "nowrap" }, format(itemHour, 'h A')))))), dayProp && /*#__PURE__*/React.createElement(Line, { gap: 0, direction: "column", align: "center", flex: true, className: classNames(['amaui-day', classes.day]) }, /*#__PURE__*/React.createElement(Line, { gap: 0, align: "center", fullWidth: true, className: classes.dayHeader }, /*#__PURE__*/React.createElement(Type, { version: "l1", weight: 200 }, format(date, 'd')), /*#__PURE__*/React.createElement(Line, { align: "center", justify: "center", className: classNames([classes.weekDay, date.year === now.year && date.dayYear === now.dayYear && classes.today]) }, /*#__PURE__*/React.createElement(Type, { version: "h3", weight: 400, align: "center" }, format(date, 'D')))), /*#__PURE__*/React.createElement(Line, { className: classes.dayBody, flex: true, fullWidth: true, onClick: event => onTimeClickMethod(date, event) }, timesUI(date), hours.map((itemGuide, indexGuide) => /*#__PURE__*/React.createElement(Line, { key: indexGuide, className: classes.guide, fullWidth: true, style: { top: `${100 / 24 * indexGuide}%` } })), format(now, formats.date) === format(date, formats.date) && /*#__PURE__*/React.createElement(Line, { className: classes.guidelineHour, fullWidth: true, style: { top: `${(now.hour * 60 + now.minute) / (24 * 60) * 100}%` } }))), !dayProp && days.map((itemDay, index) => { return /*#__PURE__*/React.createElement(Line, { key: index, gap: 0, direction: "column", align: "center", flex: true, className: classNames(['amaui-day', classes.day]) }, /*#__PURE__*/React.createElement(Line, { gap: 0, align: "center", fullWidth: true, className: classes.dayHeader }, /*#__PURE__*/React.createElement(Type, { version: "l1", weight: 200 }, format(itemDay, 'd')), /*#__PURE__*/React.createElement(Line, { align: "center", justify: "center", className: classNames([classes.weekDay, itemDay.year === now.year && itemDay.dayYear === now.dayYear && classes.today]) }, /*#__PURE__*/React.createElement(Type, { version: "h3", weight: 400, align: "center" }, format(itemDay, 'D')))), /*#__PURE__*/React.createElement(Line, { className: classes.dayBody, flex: true, fullWidth: true, onClick: event => onTimeClickMethod(itemDay, event) }, timesUI(itemDay), hours.map((itemGuide, indexGuide) => /*#__PURE__*/React.createElement(Line, { key: indexGuide, className: classes.guide, fullWidth: true, style: { top: `${100 / 24 * indexGuide}%` } })), format(now, formats.date) === format(itemDay, formats.date) && /*#__PURE__*/React.createElement(Line, { className: classes.guidelineHour, fullWidth: true, style: { top: `${(now.hour * 60 + now.minute) / (24 * 60) * 100}%` } }))); }))); }); CalendarWeek.displayName = 'amaui-CalendarWeek'; export default CalendarWeek;