@amaui/ui-react
Version:
UI for React
640 lines (639 loc) • 24.6 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(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 { capitalize, cleanValue, is, textToInnerHTML } from '@amaui/utils';
import { style as styleMethod, classNames, useAmauiTheme, colors } from '@amaui/style-react';
import { AmauiDate, add, endOf, format, remove, set, startOf } from '@amaui/date';
import IconMaterialEdit from '@amaui/icons-material-rounded-react/IconMaterialEditW100';
import IconMaterialKeyboardArrowDown from '@amaui/icons-material-rounded-react/IconMaterialKeyboardArrowDownW100';
import IconMaterialArrowForwardIos from '@amaui/icons-material-rounded-react/IconMaterialArrowForwardIosW100';
import IconMaterialArrowBackIosNew from '@amaui/icons-material-rounded-react/IconMaterialArrowBackIosNewW100';
import IconMaterialDelete from '@amaui/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,
'& .amaui-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: {
'&.amaui-Line-direction-row': {
'& > *': {
flex: '1 1 auto'
}
},
'&.amaui-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: 'amaui-CalendarAvailability'
});
const CalendarAvailability = /*#__PURE__*/React.forwardRef((props_, ref) => {
const theme = useAmauiTheme();
const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiCalendarAvailability?.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 = ['week', 'day', '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 AmauiDate());
const [date, setDate] = React.useState(dateDefault || date_ || new AmauiDate());
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 AmauiDate());
}, 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: 'Working',
value: 'working'
}, {
name: 'Not working',
value: 'not-working'
}, {
name: 'On a break',
value: 'break'
}, {
name: 'Scheduled',
value: 'pending'
}, {
name: 'Rescheduled',
value: 'rescheduled'
}, {
name: 'Cancelled',
value: 'canceled'
}, {
name: 'Other',
value: 'other'
}];
}, []);
const onToday = React.useCallback(() => {
const valueNew = new AmauiDate();
setDate(valueNew);
if (is('function', onChangeDateProps)) onChangeDateProps(valueNew);
}, [onChangeDateProps]);
const onPrevious = React.useCallback(() => {
let valueNew = new AmauiDate();
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 AmauiDate();
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 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 getDatesWeek = React.useCallback(available => {
const weekFrom = startOf(date, 'week');
const weekTo = endOf(date, 'week');
return getDates(available).filter(item => {
const from = new AmauiDate(item.from);
const to = new AmauiDate(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 'scheduled';
if (item === 'not-count-workout-session') return `don't count workout session`;
return item;
}, []);
const viewOptions = React.useMemo(() => {
return viewsProps?.map(item => ({
name: capitalize(item),
value: item
}));
}, [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')), /*#__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.')))), /*#__PURE__*/React.createElement(Line, {
gap: 2,
fullWidth: true
}, !!values.length ? values.map((itemValue, indexItem) => {
const itemValueFrom = new AmauiDate(itemValue.from);
const itemValueTo = new AmauiDate(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'), " \u2014 ", format(itemValueTo, 'hh:mm a'))), itemValue.description && /*#__PURE__*/React.createElement(Type, {
version: "b2",
weight: 200,
whiteSpace: "pre-wrap",
className: classNames([classes.timeDescription, !refs.displayTime.current && 'amaui-work-day-time']),
dangerouslySetInnerHTML: {
__html: textToInnerHTML(itemValue.description)
}
}));
}) : /*#__PURE__*/React.createElement(Type, {
version: "b2"
}, "No information for this day")));
}));
};
const simpleExceptionsUI = () => {
const items = times.flatMap(item => getDatesWeek(item));
if (!items.length) return /*#__PURE__*/React.createElement(Type, {
version: "b1"
}, "No exceptions this week");
return /*#__PURE__*/React.createElement(React.Fragment, null, items.map((itemValue, index) => {
const day = set(index + 1, 'dayWeek');
const itemValueFrom = new AmauiDate(itemValue.from);
const itemValueTo = new AmauiDate(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')), /*#__PURE__*/React.createElement(Type, {
version: "b2"
}, format(itemValueFrom, formats.entire), " \u2014 ", format(itemValueTo, formats.entire)))), itemValue.description && /*#__PURE__*/React.createElement(Type, {
version: "b2",
weight: 200,
whiteSpace: "pre-wrap",
className: classNames([classes.timeDescription, !refs.displayTime.current && 'amaui-work-day-time']),
dangerouslySetInnerHTML: {
__html: textToInnerHTML(itemValue.description)
}
}));
}));
};
const formattedDate = React.useMemo(() => {
if (view === 'day') return format(date, `MMMM DD, YYYY`);
if (['week', 'simple'].includes(view)) return `${format(startOf(date, 'week'), `MMM DD, YYYY`)} – ${format(endOf(date, 'week'), `MMM DD, YYYY`)}`;
}, [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) && ['amaui-CalendarAvailability-root'], className, classes.root])
}, other), /*#__PURE__*/React.createElement(Line, {
gap: 1,
fullWidth: true,
className: classNames([staticClassName('CalendarAvailability', theme) && ['amaui-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
}, "Today"), /*#__PURE__*/React.createElement(Line, {
gap: 0,
direction: "row",
align: "center"
}, /*#__PURE__*/React.createElement(Tooltip, {
name: `Previous ${view}`
}, /*#__PURE__*/React.createElement(IconButton, _extends({
onClick: onPrevious
}, IconButtonProps), /*#__PURE__*/React.createElement(IconPrevious, {
size: "regular"
}))), /*#__PURE__*/React.createElement(Tooltip, {
name: `Next ${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, {
value: displayTime,
onChange: onChangeDisplayTime
}, /*#__PURE__*/React.createElement(Switch, null), "Display time"), /*#__PURE__*/React.createElement(Select, {
name: "View",
value: view,
onChange: onChangeView,
options: viewOptions,
size: "small",
MenuProps: {
portal: true,
size: 'regular'
},
WrapperProps: {
style: {
width: 170,
minWidth: 'unset'
}
},
style: {
width: 170,
minWidth: 'unset'
}
}), 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) && ['amaui-CalendarAvailability-simple'], classes.calendar])
}, simpleTimesUI(), /*#__PURE__*/React.createElement(Line, {
gap: 1.5
}, /*#__PURE__*/React.createElement(Type, {
version: "t2"
}, "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: "Update"
}, /*#__PURE__*/React.createElement(IconButton, {
onClick: () => {
onUpdate(modal?.object);
onClose();
}
}, /*#__PURE__*/React.createElement(IconEdit, IconProps))), onRemove && /*#__PURE__*/React.createElement(Tooltip, {
name: "Remove"
}, /*#__PURE__*/React.createElement(IconButton, {
onClick: () => {
onRemove(modal?.object);
onClose();
}
}, /*#__PURE__*/React.createElement(IconRemove, IconProps))), /*#__PURE__*/React.createElement(Tooltip, {
name: "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'), " ", format(new AmauiDate(modal?.from), modal?.weekly ? `hh:mm a` : formats.entire), " \u2014 ", format(new AmauiDate(modal?.to), modal?.weekly ? `hh:mm a` : formats.entire)), modal?.description && /*#__PURE__*/React.createElement(Type, {
version: "b2",
dangerouslySetInnerHTML: {
__html: textToInnerHTML(modal.description)
}
})))));
});
CalendarAvailability.displayName = 'amaui-CalendarAvailability';
export default CalendarAvailability;