@onesy/ui-react
Version:
UI for React
544 lines (541 loc) • 19.8 kB
JavaScript
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(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React from 'react';
import { cleanValue, is, textToInnerHTML } from '@onesy/utils';
import { style as styleMethod, classNames, useOnesyTheme, colors } from '@onesy/style-react';
import { OnesyDate, add, endOf, format, set, startOf } from '@onesy/date';
import LineElement from '../Line';
import TypeElement from '../Type';
import TooltipElement from '../Tooltip';
import { formats, staticClassName } from '../utils';
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
const useStyle = styleMethod(theme => ({
root: {
color: theme.methods.palette.color.value('primary', 10),
background: theme.palette.background.default.primary,
paddingLeft: '12px'
},
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')
},
'& > .onesy-work-day-time': {
opacity: 0
},
'&:hover': {
zIndex: 14,
boxShadow: theme.shadows.values.default[2],
'& > .onesy-work-day-time': {
opacity: 1
}
}
},
time: {
position: 'relative',
zIndex: 1,
padding: '0px 2px',
borderRadius: 2
}
}), {
name: 'onesy-CalendarWeek'
});
const CalendarWeek = props_ => {
const theme = useOnesyTheme();
const l = theme.l;
const props = _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.onesyCalendarWeek?.props?.default), props_);
const Line = theme?.elements?.Line || LineElement;
const Type = theme?.elements?.Type || TypeElement;
const Tooltip = theme?.elements?.Tooltip || TooltipElement;
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 OnesyDate());
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 OnesyDate());
}, 60 * 1e3);
return () => {
clearInterval(refs.interval.current);
};
}, []);
const getDates = available => {
const values = available.dates?.values || [];
return values.map(item => {
if (item.entire) {
if (item.from) {
let from = new OnesyDate(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;
});
};
const optionsStatus = React.useMemo(() => {
return [{
name: l('Working'),
value: 'working'
}, {
name: l('Not working'),
value: 'not-working'
}, {
name: l('On a break'),
value: 'break'
}, {
name: l('Scheduled'),
value: 'pending'
}, {
name: l('Rescheduled'),
value: 'rescheduled'
}, {
name: l('Cancelled'),
value: 'canceled'
}, {
name: l('Other'),
value: 'other'
}];
}, []);
const getColor = item_0 => {
let palette = theme.palette.color.neutral;
if (item_0?.status === 'working') palette = theme.palette.color.success;
if (item_0?.status === 'not-working') palette = theme.palette.color.info;
if (item_0?.status === 'break') palette = theme.palette.color.warning;
if (item_0?.status === 'pending') palette = theme.methods.color(colors.yellow[50]);
if (item_0?.status === 'rescheduled') palette = theme.methods.color(colors.purple[50]);
if (item_0?.status === 'canceled') palette = theme.palette.color.error;
if (item_0?.status === 'other') palette = theme.palette.color.neutral;
if (item_0.color) palette = theme.methods.color(item_0.color);
return palette[rangeShade];
};
const itemToText = item_1 => {
if (item_1 === 'pending') return l('Scheduled');
if (item_1 === 'not-count-workout-session') return l(`Don't count workout session`);
return optionsStatus?.find(itemStatus => itemStatus.value === item_1)?.name ?? l(item_1);
};
const renderTimes = (day, valuesAll, weekly = true, itemDay) => {
if (itemDay !== undefined && !itemDay?.active) return null;
if (weekly) {
const ends_at = itemDay?.ends_at ? new OnesyDate(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_0 = valuesAll?.filter(item_2 => {
const from_0 = new OnesyDate(item_2.from);
const fromStartOfDay = startOf(from_0, 'day');
const to_0 = new OnesyDate(item_2.to);
const toStartOfDay = startOf(to_0, 'day');
return [undefined, true].includes(refs.statuses.current[item_2.status || 'working']) && (weekly ? weekly :
// from
(dayDate === format(from_0, formats.date) || dayStartOfDay.milliseconds > fromStartOfDay.milliseconds) && (
// to
dayDate === format(to_0, formats.date) || dayStartOfDay.milliseconds < toStartOfDay.milliseconds));
});
const elements = [];
const renderTo = valueNew => {
if (valueNew === '23:59') return '24:00';
return valueNew;
};
values_0?.forEach((item_3, index) => {
if (!(item_3.from && item_3.to)) return;
let from_1 = new OnesyDate(item_3.from);
let to_1 = new OnesyDate(item_3.to);
if (!weekly) {
const fromStartOfDay_0 = startOf(from_1, 'day');
const toStartOfDay_0 = startOf(to_1, 'day');
if (dayStartOfDay?.milliseconds > fromStartOfDay_0?.milliseconds && (dayDate === format(to_1, formats.date) || dayStartOfDay.milliseconds < toStartOfDay_0.milliseconds)) item_3.from = startOf(from_1, 'day').milliseconds;
if ((dayDate === format(from_1, formats.date) || dayStartOfDay.milliseconds > fromStartOfDay_0.milliseconds) && dayStartOfDay?.milliseconds < toStartOfDay_0?.milliseconds) item_3.to = endOf(to_1, 'day').milliseconds;
}
from_1 = new OnesyDate(item_3.from);
to_1 = new OnesyDate(item_3.to);
const itemDate = format(day, formats.date);
const top = +(100 * ((from_1.hour * 60 + from_1.minute) / (24 * 60))).toFixed(4);
const bottom = +(100 - 100 * ((to_1.hour * 60 + (to_1.minute === 59 ? 60 : to_1.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(([itemTop, itemBottom]) => {
return !(top >= itemBottom || bottom_ <= itemTop);
});
let level = 0;
if (overlaps.length) {
level = refs.overlaping.current[itemDate].filter(([itemTop_0, itemBottom_0]) => {
return !(top >= itemBottom_0 || bottom_ <= itemTop_0);
}).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_3);
const WrapperElement = item_3.status ? Tooltip : React.Fragment;
const WrapperElementProps = item_3.status ? {
name: cleanValue(itemToText(item_3.status), {
capitalize: true
}),
color: getColor(item_3)
} : undefined;
const itemProps = {
onClick: event => onOpen(_objectSpread(_objectSpread({}, item_3), {}, {
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_3, dayProp ? 'day' : 'week') : /*#__PURE__*/_jsxs(Line, _objectSpread(_objectSpread({
gap: 0.5,
align: "center",
justify: "center"
}, itemProps), {}, {
children: [!minimal && refs.displayTime.current && /*#__PURE__*/_jsxs(Type, {
version: "l2",
weight: 300,
align: "center",
className: classes.time,
style: {
background: getColor(item_3)
},
children: [format(from_1, 'hh:mm a', {
l
}), " - ", renderTo(format(to_1, 'hh:mm a', {
l
}))]
}), item_3.description && /*#__PURE__*/_jsx(Type, {
version: "b2",
dangerouslySetInnerHTML: {
__html: textToInnerHTML(item_3.description)
},
style: {
maxHeight: 24,
maxWidth: '90%'
}
})]
}), index);
elements.push(/*#__PURE__*/_jsx(WrapperElement, _objectSpread(_objectSpread({}, WrapperElementProps), {}, {
children: /*#__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 = (itemDay_0, event_0) => {
const rect = event_0.currentTarget.getBoundingClientRect();
const relativeY = event_0.clientY - rect.top;
const relativePercentage = relativeY / rect.height * 100;
let timeDate = set(Math.floor(24 * (relativePercentage / 100)), 'hour', itemDay_0);
// start of the hour
timeDate = startOf(timeDate, 'hour');
onTimeClick?.(timeDate, dayProp ? 'day' : 'week', event_0);
};
const timesUI = dayDate_0 => {
// clean up
refs.days.current = {};
refs.overlaping.current = {};
const day_0 = dayDate_0.dayWeek === 0 ? 7 : dayDate_0.dayWeek;
return /*#__PURE__*/_jsx(_Fragment, {
children: times.map(item_4 => /*#__PURE__*/_jsxs(_Fragment, {
children: [item_4.weekly && renderTimes(dayDate_0, item_4.weekly.days[day_0]?.values, true, item_4.weekly.days[day_0]), item_4.dates && renderTimes(dayDate_0, getDates(item_4), false)]
}))
});
};
const hours = React.useMemo(() => {
return Array.from({
length: 24
}).map((item_5, index_0) => set(index_0, 'hour', date));
}, [date]);
const days = React.useMemo(() => {
const weekStartDate = set(4, 'hour', startOf(date, 'week'));
return Array.from({
length: 7
}).map((_, index_1) => add(index_1, 'day', weekStartDate));
}, [date]);
return /*#__PURE__*/_jsx(Line, _objectSpread(_objectSpread({
gap: 1.5,
flex: true,
fullWidth: true,
className: classNames([staticClassName('CalendarWeek', theme) && ['onesy-CalendarWeek-root', dayProp && 'onesy-CalendarWeek-prop-day'], className, classes.root])
}, other), {}, {
children: /*#__PURE__*/_jsxs(Line, {
gap: 0,
direction: "row",
align: "stretch",
flex: true,
fullWidth: true,
className: classes.days,
children: [/*#__PURE__*/_jsxs(Line, {
gap: 0,
align: "center",
fullWidth: true,
className: classNames(['onesy-hours', classes.hours]),
children: [/*#__PURE__*/_jsx(Line, {
className: classes.dayHeader
}), /*#__PURE__*/_jsx(Line, {
gap: 0,
flex: true,
fullWidth: true,
className: classes.hourWrapper,
children: hours.map((itemHour, index_2) => /*#__PURE__*/_jsx(Line, {
gap: 1,
align: "unset",
className: classes.hour,
style: {
top: `${100 / 24 * index_2}%`
},
children: /*#__PURE__*/_jsx(Type, {
version: "b3",
whiteSpace: "nowrap",
children: format(itemHour, 'h A', {
l
})
})
}, index_2))
})]
}), dayProp && /*#__PURE__*/_jsxs(Line, {
gap: 0,
direction: "column",
align: "center",
flex: true,
className: classNames(['onesy-day', classes.day]),
children: [/*#__PURE__*/_jsxs(Line, {
gap: 0,
align: "center",
fullWidth: true,
className: classes.dayHeader,
children: [/*#__PURE__*/_jsx(Type, {
version: "l1",
weight: 200,
children: format(date, 'd', {
l
})
}), /*#__PURE__*/_jsx(Line, {
align: "center",
justify: "center",
className: classNames([classes.weekDay, date.year === now.year && date.dayYear === now.dayYear && classes.today]),
children: /*#__PURE__*/_jsx(Type, {
version: "h3",
weight: 400,
align: "center",
children: format(date, 'D', {
l
})
})
})]
}), /*#__PURE__*/_jsxs(Line, {
className: classes.dayBody,
flex: true,
fullWidth: true,
onClick: event_1 => onTimeClickMethod(date, event_1),
children: [timesUI(date), hours.map((itemGuide, indexGuide) => /*#__PURE__*/_jsx(Line, {
className: classes.guide,
fullWidth: true,
style: {
top: `${100 / 24 * indexGuide}%`
}
}, indexGuide)), format(now, formats.date) === format(date, formats.date) && /*#__PURE__*/_jsx(Line, {
className: classes.guidelineHour,
fullWidth: true,
style: {
top: `${(now.hour * 60 + now.minute) / (24 * 60) * 100}%`
}
})]
})]
}), !dayProp && days.map((itemDay_1, index_3) => {
return /*#__PURE__*/_jsxs(Line, {
gap: 0,
direction: "column",
align: "center",
flex: true,
className: classNames(['onesy-day', classes.day]),
children: [/*#__PURE__*/_jsxs(Line, {
gap: 0,
align: "center",
fullWidth: true,
className: classes.dayHeader,
children: [/*#__PURE__*/_jsx(Type, {
version: "l1",
weight: 200,
children: format(itemDay_1, 'd', {
l
})
}), /*#__PURE__*/_jsx(Line, {
align: "center",
justify: "center",
className: classNames([classes.weekDay, itemDay_1.year === now.year && itemDay_1.dayYear === now.dayYear && classes.today]),
children: /*#__PURE__*/_jsx(Type, {
version: "h3",
weight: 400,
align: "center",
children: format(itemDay_1, 'D', {
l
})
})
})]
}), /*#__PURE__*/_jsxs(Line, {
className: classes.dayBody,
flex: true,
fullWidth: true,
onClick: event_2 => onTimeClickMethod(itemDay_1, event_2),
children: [timesUI(itemDay_1), hours.map((itemGuide_0, indexGuide_0) => /*#__PURE__*/_jsx(Line, {
className: classes.guide,
fullWidth: true,
style: {
top: `${100 / 24 * indexGuide_0}%`
}
}, indexGuide_0)), format(now, formats.date) === format(itemDay_1, formats.date) && /*#__PURE__*/_jsx(Line, {
className: classes.guidelineHour,
fullWidth: true,
style: {
top: `${(now.hour * 60 + now.minute) / (24 * 60) * 100}%`
}
})]
})]
}, index_3);
})]
})
}));
};
CalendarWeek.displayName = 'onesy-CalendarWeek';
export default CalendarWeek;