@onesy/ui-react
Version:
UI for React
666 lines (665 loc) • 24.9 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
const _excluded = ["name", "date", "dateDefault", "times", "events", "meta", "views", "onUpdate", "onRemove", "onChangeDate", "startHeader", "endHeader", "startLeft", "endLeft", "startRight", "endRight", "startLeftModal", "endLeftModal", "startRightModal", "endRightModal", "Component", "IconEdit", "IconPrevious", "IconNext", "IconRemove", "IconClose", "WeekProps", "DayProps", "IconProps", "IconButtonProps", "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 { capitalize, cleanValue, is, textToInnerHTML } from '@onesy/utils';
import { style as styleMethod, classNames, useOnesyTheme, colors } from '@onesy/style-react';
import { OnesyDate, add, endOf, format, remove, set, startOf } from '@onesy/date';
import IconMaterialEdit from '@onesy/icons-material-rounded-react/IconMaterialEditW100';
import IconMaterialKeyboardArrowDown from '@onesy/icons-material-rounded-react/IconMaterialKeyboardArrowDownW100';
import IconMaterialArrowForwardIos from '@onesy/icons-material-rounded-react/IconMaterialArrowForwardIosW100';
import IconMaterialArrowBackIosNew from '@onesy/icons-material-rounded-react/IconMaterialArrowBackIosNewW100';
import IconMaterialDelete from '@onesy/icons-material-rounded-react/IconMaterialDeleteW100';
import CalendarWeekElement from '../CalendarWeek';
import SelectElement from '../Select';
import ButtonElement from '../Button';
import LineElement from '../Line';
import ModalElement from '../Modal';
import ModalHeaderElement from '../ModalHeader';
import ModalMainElement from '../ModalMain';
import TypeElement from '../Type';
import TooltipElement from '../Tooltip';
import IconButtonElement from '../IconButton';
import LabelElement from '../Label';
import SlideElement from '../Slide';
import SwitchElement from '../Switch';
import { formats, staticClassName } from '../utils';
const useStyle = styleMethod(theme => ({
root: {
padding: '16px',
paddingBottom: '24px',
color: theme.methods.palette.color.value('primary', 10),
background: theme.palette.background.default.primary,
'& .onesy-Label-text': {
whiteSpace: 'nowrap'
}
},
calendar: {
padding: '12px 8px',
background: theme.palette.background.default.primary
},
aside: {
width: 'auto',
maxWidth: '100%'
},
weekDay: {
width: '47px',
height: '47px',
borderRadius: '50%'
},
today: {
background: theme.palette.color.primary[40],
color: '#fff'
},
palettePreview: {
width: '17px',
height: '17px',
boxShadow: theme.palette.light ? '0px 1px 1px 0px rgba(0, 0, 0, 0.07), 0px 2px 1px -1px rgba(0, 0, 0, 0.04), 0px 1px 3px 0px rgba(0, 0, 0, 0.11)' : '0px 1px 1px 0px rgba(255, 255, 255, 0.21), 0px 2px 1px -1px rgba(255, 255, 255, 0.12), 0px 1px 3px 0px rgba(255, 255, 255, 0.40)',
borderRadius: '50%',
cursor: 'default',
flex: '0 0 auto',
transition: theme.methods.transitions.make('transform'),
'& > *': {
width: '100% !important',
height: 'calc(100% + 12px) !important'
},
'&:active': {
transform: 'scale(0.94)'
}
},
dayWeekSimple: {
maxWidth: 184
},
simpleTimes: {
'&.onesy-Line-direction-row': {
'& > *': {
flex: '1 1 auto'
}
},
'&.onesy-Line-direction-column': {
'& > *': {
width: '100%'
}
}
},
legend: {
padding: '2px',
alignSelf: 'center',
maxWidth: '100%',
overflow: 'auto hidden'
},
itemLegend: {
cursor: 'pointer',
userSelect: 'none',
opacity: 0.5,
transition: theme.methods.transitions.make(['opacity', 'transform']),
'&:active': {
transform: 'scale(0.94)'
}
},
itemLegendActive: {
opacity: 1
},
overflowX: {
padding: '2px 0',
overflow: 'auto hidden'
}
}), {
name: 'onesy-CalendarAvailability'
});
const CalendarAvailability = /*#__PURE__*/React.forwardRef((props_, ref) => {
const theme = useOnesyTheme();
const l = theme.l;
const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.onesyCalendarAvailability?.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 IconButton = React.useMemo(() => theme?.elements?.IconButton || IconButtonElement, [theme]);
const Label = React.useMemo(() => theme?.elements?.Label || LabelElement, [theme]);
const Switch = React.useMemo(() => theme?.elements?.Switch || SwitchElement, [theme]);
const Modal = React.useMemo(() => theme?.elements?.Modal || ModalElement, [theme]);
const ModalHeader = React.useMemo(() => theme?.elements?.ModalHeader || ModalHeaderElement, [theme]);
const ModalMain = React.useMemo(() => theme?.elements?.ModalMain || ModalMainElement, [theme]);
const Slide = React.useMemo(() => theme?.elements?.Slide || SlideElement, [theme]);
const Button = React.useMemo(() => theme?.elements?.Button || ButtonElement, [theme]);
const Select = React.useMemo(() => theme?.elements?.Select || SelectElement, [theme]);
const CalendarWeek = React.useMemo(() => theme?.elements?.CalendarWeek || CalendarWeekElement, [theme]);
const {
name,
date: date_,
dateDefault,
times: timesProps,
events,
meta,
views: viewsProps = [{
name: l('Week'),
value: 'week'
}, {
name: l('Day'),
value: 'day'
}, {
name: l('Simple'),
value: 'simple'
}],
onUpdate,
onRemove,
onChangeDate: onChangeDateProps,
startHeader,
endHeader,
startLeft,
endLeft,
startRight,
endRight,
startLeftModal,
endLeftModal,
startRightModal,
endRightModal,
Component = Line,
IconEdit = IconMaterialEdit,
IconPrevious = IconMaterialArrowBackIosNew,
IconNext = IconMaterialArrowForwardIos,
IconRemove = IconMaterialDelete,
IconClose = IconMaterialKeyboardArrowDown,
WeekProps,
DayProps,
IconProps,
IconButtonProps,
className,
children
} = props,
other = _objectWithoutProperties(props, _excluded);
const {
classes
} = useStyle();
const [now, setNow] = React.useState(new OnesyDate());
const [date, setDate] = React.useState(dateDefault || date_ || new OnesyDate());
const [view, setView] = React.useState('week');
const [displayTime, setDisplayTime] = React.useState(true);
const [modal, setModal] = React.useState();
const [statuses, setStatuses] = React.useState({});
const refs = {
date: React.useRef(date),
displayTime: React.useRef(displayTime),
interval: React.useRef(undefined),
calendar: 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 onStatusToggle = React.useCallback(function () {
let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'working';
setStatuses(previous => _objectSpread(_objectSpread({}, previous), {}, {
[value]: previous[value] === undefined ? false : !previous[value]
}));
}, []);
const rangeShade = theme.palette.light ? 70 : 40;
React.useEffect(() => {
// 1 minute
refs.interval.current = setInterval(() => {
setNow(new OnesyDate());
}, 60 * 1e3);
return () => {
clearInterval(refs.interval.current);
};
}, []);
// Date
React.useEffect(() => {
if (date_ !== undefined && date_ !== date) setDate(date_);
}, [date_]);
const onOpen = React.useCallback(item => {
setModal(_objectSpread(_objectSpread({}, item), {}, {
open: true
}));
}, []);
const onClose = React.useCallback(() => {
setModal(item => _objectSpread(_objectSpread({}, item), {}, {
open: false
}));
}, []);
const onChangeDisplayTime = React.useCallback(valueNew => {
setDisplayTime(valueNew);
}, []);
const onChangeView = React.useCallback(valueNew => {
setView(valueNew);
}, []);
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 onToday = React.useCallback(() => {
const valueNew = new OnesyDate();
setDate(valueNew);
if (is('function', onChangeDateProps)) onChangeDateProps(valueNew);
}, [onChangeDateProps]);
const onPrevious = React.useCallback(() => {
let valueNew = new OnesyDate();
setDate(previous => {
valueNew = remove(1, ['week', 'simple'].includes(view) ? 'week' : view, previous);
return valueNew;
});
if (is('function', onChangeDateProps)) onChangeDateProps(valueNew);
}, [view, onChangeDateProps]);
const onNext = React.useCallback(() => {
let valueNew = new OnesyDate();
setDate(previous => {
valueNew = add(1, ['week', 'simple'].includes(view) ? 'week' : view, previous);
return valueNew;
});
if (is('function', onChangeDateProps)) onChangeDateProps(valueNew);
}, [view, onChangeDateProps]);
const getDates = React.useCallback(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;
});
}, [times]);
const getDatesWeek = React.useCallback(available => {
const weekFrom = startOf(date, 'week');
const weekTo = endOf(date, 'week');
return getDates(available).filter(item => {
const from = new OnesyDate(item.from);
const to = new OnesyDate(item.to);
return !(from.milliseconds >= weekTo.milliseconds || to.milliseconds <= weekFrom.milliseconds);
});
}, [date, getDates]);
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;
return palette[rangeShade];
}, [rangeShade, colors, theme]);
const itemToText = React.useCallback(item => {
if (item === 'pending') return l('Scheduled');
if (item === 'not-count-workout-session') return l(`Don't count workout session`);
return optionsStatus?.find(itemStatus => itemStatus.value === item)?.name ?? l(item);
}, []);
const viewOptions = React.useMemo(() => {
return viewsProps?.map(item => ({
name: capitalize(item?.name),
value: item?.value
}));
}, [viewsProps]);
const days = React.useMemo(() => {
const weekStartDate = set(4, 'hour', startOf(date, 'week'));
return Array.from({
length: 7
}).map((_, index) => add(index, 'day', weekStartDate));
}, [date]);
const simpleTimesUI = () => {
return /*#__PURE__*/React.createElement(Line, {
gap: 3,
direction: {
default: 'row',
1400: 'column'
},
className: classes.simpleTimes,
fullWidth: true
}, days.map((itemDay, index) => {
const values = times.filter(item => item.weekly.days[index + 1]?.active).flatMap(item => item.weekly.days[index + 1].values).filter(item => item && [undefined, true].includes(refs.statuses.current[item.status || 'working']));
values.sort((a, b) => b.from > a.from ? -1 : 1);
return /*#__PURE__*/React.createElement(Line, {
key: index,
gap: 1.5,
direction: "column"
}, /*#__PURE__*/React.createElement(Line, {
gap: 0,
align: "center",
fullWidth: true
}, /*#__PURE__*/React.createElement(Type, {
version: "h3",
weight: 400
}, format(itemDay, 'dd', {
l
})), /*#__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: "b2",
weight: 200
}, format(itemDay, 'DD.MM.', {
l
})))), /*#__PURE__*/React.createElement(Line, {
gap: 2,
fullWidth: true
}, !!values.length ? values.map((itemValue, indexItem) => {
const itemValueFrom = new OnesyDate(itemValue.from);
const itemValueTo = new OnesyDate(itemValue.to);
return /*#__PURE__*/React.createElement(Line, {
key: indexItem,
gap: 0.5,
className: classes.dayWeekSimple
}, /*#__PURE__*/React.createElement(Line, {
gap: 1,
direction: "row",
align: "center"
}, /*#__PURE__*/React.createElement(Line, {
className: classes.palettePreview,
style: {
background: getColor(itemValue)
}
}), /*#__PURE__*/React.createElement(Type, {
version: "b2",
weight: 300
}, format(itemValueFrom, 'hh:mm a', {
l
}), " \u2014 ", format(itemValueTo, 'hh:mm a', {
l
}))), itemValue.description && /*#__PURE__*/React.createElement(Type, {
version: "b2",
weight: 200,
whiteSpace: "pre-wrap",
className: classNames([classes.timeDescription, !refs.displayTime.current && 'onesy-work-day-time']),
dangerouslySetInnerHTML: {
__html: textToInnerHTML(itemValue.description)
}
}));
}) : /*#__PURE__*/React.createElement(Type, {
version: "b2"
}, l('No information for this day'))));
}));
};
const simpleExceptionsUI = () => {
const items = times.flatMap(item => getDatesWeek(item));
if (!items.length) return /*#__PURE__*/React.createElement(Type, {
version: "b1"
}, l('No exceptions this week'));
return /*#__PURE__*/React.createElement(React.Fragment, null, items.map((itemValue, index) => {
const day = set(index + 1, 'dayWeek');
const itemValueFrom = new OnesyDate(itemValue.from);
const itemValueTo = new OnesyDate(itemValue.to);
return /*#__PURE__*/React.createElement(Line, {
key: index,
gap: 1,
direction: "column"
}, /*#__PURE__*/React.createElement(Line, {
gap: 1,
direction: "row",
align: "center",
fullWidth: true
}, /*#__PURE__*/React.createElement(Line, {
className: classes.palettePreview,
style: {
background: getColor(itemValue)
}
}), /*#__PURE__*/React.createElement(Line, {
gap: 1,
direction: "row",
align: "center"
}, /*#__PURE__*/React.createElement(Type, {
version: "b2",
weight: 400
}, format(day, 'dd', {
l
})), /*#__PURE__*/React.createElement(Type, {
version: "b2"
}, format(itemValueFrom, formats.entire, {
l
}), " \u2014 ", format(itemValueTo, formats.entire, {
l
})))), itemValue.description && /*#__PURE__*/React.createElement(Type, {
version: "b2",
weight: 200,
whiteSpace: "pre-wrap",
className: classNames([classes.timeDescription, !refs.displayTime.current && 'onesy-work-day-time']),
dangerouslySetInnerHTML: {
__html: textToInnerHTML(itemValue.description)
}
}));
}));
};
const formattedDate = React.useMemo(() => {
if (view === 'day') return format(date, `MMMM DD, YYYY`, {
l
});
if (['week', 'simple'].includes(view)) return `${format(startOf(date, 'week'), `MMM DD, YYYY`, {
l
})} – ${format(endOf(date, 'week'), `MMM DD, YYYY`, {
l
})}`;
}, [view, date]);
const legend = React.useMemo(() => {
return /*#__PURE__*/React.createElement(Line, {
rowGap: 1.5,
direction: "row",
align: "center",
className: classes.legend
}, optionsStatus.map((item, index) => /*#__PURE__*/React.createElement(Line, {
key: index,
gap: 1,
direction: "row",
align: "center",
onClick: () => onStatusToggle(item.value),
flexNo: true,
className: classNames([classes.itemLegend, [undefined, true].includes(refs.statuses.current[item.value || 'working']) && classes.itemLegendActive])
}, /*#__PURE__*/React.createElement(Line, {
className: classes.palettePreview,
style: {
background: getColor({
status: item.value
})
}
}), /*#__PURE__*/React.createElement(Type, {
version: "b2"
}, item.name))));
}, [theme, statuses, optionsStatus]);
return /*#__PURE__*/React.createElement(Component, _extends({
ref: ref,
flex: true,
fullWidth: true,
className: classNames([staticClassName('CalendarAvailability', theme) && ['onesy-CalendarAvailability-root'], className, classes.root])
}, other), /*#__PURE__*/React.createElement(Line, {
gap: 1,
fullWidth: true,
className: classNames([staticClassName('CalendarAvailability', theme) && ['onesy-CalendarAvailability-header']])
}, startHeader, /*#__PURE__*/React.createElement(Line, {
gap: 2,
direction: "row",
wrap: "wrap",
justify: "space-between",
align: "center",
fullWidth: true
}, /*#__PURE__*/React.createElement(Line, {
gap: 1.5,
direction: "row",
wrap: "wrap",
align: "center",
className: classes.aside
}, startLeft, /*#__PURE__*/React.createElement(Button, {
color: "inherit",
version: "outlined",
size: "small",
onClick: onToday,
selected: now.days === date.days
}, l('Today')), /*#__PURE__*/React.createElement(Line, {
gap: 0,
direction: "row",
align: "center"
}, /*#__PURE__*/React.createElement(Tooltip, {
name: `${l('Previous')} ${l(view)}`
}, /*#__PURE__*/React.createElement(IconButton, _extends({
onClick: onPrevious
}, IconButtonProps), /*#__PURE__*/React.createElement(IconPrevious, {
size: "regular"
}))), /*#__PURE__*/React.createElement(Tooltip, {
name: `${l('Next')} ${l(view)}`
}, /*#__PURE__*/React.createElement(IconButton, _extends({
onClick: onNext
}, IconButtonProps), /*#__PURE__*/React.createElement(IconNext, {
size: "regular"
})))), /*#__PURE__*/React.createElement(Type, {
version: "h2",
weight: 500,
whiteSpace: "nowrap"
}, formattedDate), endLeft), /*#__PURE__*/React.createElement(Line, {
gap: 1.5,
direction: "row",
align: "center",
flexNo: true,
className: classNames([classes.aside, classes.overflowX])
}, startRight, ['week', 'day'].includes(view) && /*#__PURE__*/React.createElement(Label, {
checked: displayTime,
onChange: onChangeDisplayTime
}, /*#__PURE__*/React.createElement(Switch, null), l('Display time')), /*#__PURE__*/React.createElement(Select, {
name: l('View'),
value: view,
onChange: onChangeView,
options: viewOptions,
size: "small",
MenuProps: {
portal: true,
size: 'regular'
}
}), endRight)), endHeader), /*#__PURE__*/React.createElement(Line, {
ref: refs.calendar,
gap: 2,
flex: true,
fullWidth: true
}, view === 'simple' && /*#__PURE__*/React.createElement(Line, {
gap: 1,
flex: true,
fullWidth: true
}, /*#__PURE__*/React.createElement(Line, {
gap: 4,
fullWidth: true,
className: classNames([staticClassName('CalendarAvailability', theme) && ['onesy-CalendarAvailability-simple'], classes.calendar])
}, simpleTimesUI(), /*#__PURE__*/React.createElement(Line, {
gap: 1.5
}, /*#__PURE__*/React.createElement(Type, {
version: "t2"
}, l('Exceptions this week')), /*#__PURE__*/React.createElement(Line, {
gap: 1
}, simpleExceptionsUI())))), ['week', 'day'].includes(view) && /*#__PURE__*/React.createElement(CalendarWeek, _extends({
onOpen: onOpen,
date: date,
displayTime: displayTime,
statuses: statuses,
times: times,
events: events,
day: view === 'day'
}, WeekProps)), legend), times && /*#__PURE__*/React.createElement(Modal, {
open: !!modal?.open,
onClose: onClose,
minWidth: "lg",
TransitionComponent: Slide,
size: "small"
}, /*#__PURE__*/React.createElement(ModalHeader, {
gap: 1,
direction: "row",
align: "center",
justify: "space-between",
fullWidth: true
}, /*#__PURE__*/React.createElement(Line, {
gap: 1,
direction: "row",
align: "center"
}, startLeftModal, /*#__PURE__*/React.createElement(Line, {
className: classes.palettePreview,
style: {
background: getColor(modal)
}
}), /*#__PURE__*/React.createElement(Type, {
version: "b2",
weight: 100
}, cleanValue(itemToText(modal?.status), {
capitalize: true
})), endLeftModal), /*#__PURE__*/React.createElement(Line, {
gap: 1,
direction: "row",
align: "center"
}, startRightModal, onUpdate && /*#__PURE__*/React.createElement(Tooltip, {
name: l('Update')
}, /*#__PURE__*/React.createElement(IconButton, {
onClick: () => {
onUpdate(modal?.object);
onClose();
}
}, /*#__PURE__*/React.createElement(IconEdit, IconProps))), onRemove && /*#__PURE__*/React.createElement(Tooltip, {
name: l('Remove')
}, /*#__PURE__*/React.createElement(IconButton, {
onClick: () => {
onRemove(modal?.object);
onClose();
}
}, /*#__PURE__*/React.createElement(IconRemove, IconProps))), /*#__PURE__*/React.createElement(Tooltip, {
name: l('Close')
}, /*#__PURE__*/React.createElement(IconButton, {
onClick: onClose
}, /*#__PURE__*/React.createElement(IconClose, IconProps))), endRightModal)), /*#__PURE__*/React.createElement(ModalMain, {
align: "flex-start",
className: classes.modalMain
}, /*#__PURE__*/React.createElement(Line, {
gap: 1.5,
fullWidth: true
}, /*#__PURE__*/React.createElement(Type, {
version: "l1",
weight: 200
}, format(modal?.day, 'dd', {
l
}), " ", format(new OnesyDate(modal?.from), modal?.weekly ? `hh:mm a` : formats.entire, {
l
}), " \u2014 ", format(new OnesyDate(modal?.to), modal?.weekly ? `hh:mm a` : formats.entire, {
l
})), modal?.description && /*#__PURE__*/React.createElement(Type, {
version: "b2",
dangerouslySetInnerHTML: {
__html: textToInnerHTML(modal.description)
}
})))));
});
CalendarAvailability.displayName = 'onesy-CalendarAvailability';
export default CalendarAvailability;