@trackunit/react-date-and-time-components 0.0.203 → 0.0.204
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +197 -11
- package/index.esm.js +189 -5
- package/package.json +4 -2
- package/src/Timeline/Timeline.d.ts +27 -0
- package/src/Timeline/Timeline.stories.d.ts +13 -0
- package/src/Timeline/Timeline.variants.d.ts +15 -0
- package/src/Timeline/TimelineElement.d.ts +29 -0
- package/src/Timeline/index.d.ts +2 -0
- package/src/index.d.ts +1 -0
package/index.cjs.js
CHANGED
|
@@ -5,13 +5,14 @@ var i18nLibraryTranslation = require('@trackunit/i18n-library-translation');
|
|
|
5
5
|
var dateAndTimeUtils = require('@trackunit/date-and-time-utils');
|
|
6
6
|
var reactDateAndTimeHooks = require('@trackunit/react-date-and-time-hooks');
|
|
7
7
|
var uiDesignTokens = require('@trackunit/ui-design-tokens');
|
|
8
|
-
var
|
|
8
|
+
var React = require('react');
|
|
9
9
|
var reactComponents = require('@trackunit/react-components');
|
|
10
10
|
var sharedUtils = require('@trackunit/shared-utils');
|
|
11
11
|
var reactDayPicker = require('react-day-picker');
|
|
12
12
|
var tailwindMerge = require('tailwind-merge');
|
|
13
13
|
var locale = require('date-fns/locale');
|
|
14
14
|
var reactCoreHooks = require('@trackunit/react-core-hooks');
|
|
15
|
+
var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
|
|
15
16
|
|
|
16
17
|
var defaultTranslations = {
|
|
17
18
|
"dateTime.instant.now": "Now",
|
|
@@ -90,18 +91,18 @@ const MS_PER_HOUR = 60 * 60 * 1000;
|
|
|
90
91
|
const DateTime = ({ value, format, className, fromNow, withTitle, titleFormat, timezone, subtle, calendar, }) => {
|
|
91
92
|
const { t } = useTranslation();
|
|
92
93
|
const locale = reactDateAndTimeHooks.useLocale();
|
|
93
|
-
const nowDate =
|
|
94
|
+
const nowDate = React.useMemo(() => new Date(), []);
|
|
94
95
|
const date = value ? dateAndTimeUtils.toDateUtil(value) : nowDate;
|
|
95
96
|
const newDateTime = dateAndTimeUtils.formatDateUtil(date, format, timezone === null || timezone === void 0 ? void 0 : timezone.id, locale);
|
|
96
97
|
const titleDateTime = withTitle ? dateAndTimeUtils.formatDateUtil(date, titleFormat, timezone === null || timezone === void 0 ? void 0 : timezone.id, locale) : undefined;
|
|
97
|
-
const getTimeSince =
|
|
98
|
+
const getTimeSince = React.useCallback((from, to) => {
|
|
98
99
|
const same = dateAndTimeUtils.isEqualUtil(from, to);
|
|
99
100
|
if (same) {
|
|
100
101
|
return t("dateTime.instant.now");
|
|
101
102
|
}
|
|
102
103
|
return dateAndTimeUtils.timeSinceAuto(from, to, timezone === null || timezone === void 0 ? void 0 : timezone.id, locale);
|
|
103
104
|
}, [locale, t, timezone === null || timezone === void 0 ? void 0 : timezone.id]);
|
|
104
|
-
const dateValue =
|
|
105
|
+
const dateValue = React.useMemo(() => {
|
|
105
106
|
return fromNow ? getTimeSince(date, nowDate) : newDateTime;
|
|
106
107
|
}, [date, fromNow, getTimeSince, newDateTime, nowDate]);
|
|
107
108
|
return (jsxRuntime.jsx("time", { className: className, dateTime: newDateTime, style: subtle ? { color: uiDesignTokens.color("NEUTRAL", 600, "CSS") } : undefined, title: titleDateTime, children: dateValue }));
|
|
@@ -168,15 +169,15 @@ const DayPicker = ({ onDaySelect, disabledDays, selectedDays, language, classNam
|
|
|
168
169
|
* @returns {JSX.Element} DayRangePicker component
|
|
169
170
|
*/
|
|
170
171
|
const DayRangePicker = ({ onRangeSelect, selectedDays, disabledDays, dataTestId, language, className, max, showQuickOptions, timezone, onClose, }) => {
|
|
171
|
-
const [newRange, setNewRange] =
|
|
172
|
+
const [newRange, setNewRange] = React.useState(selectedDays !== null && selectedDays !== void 0 ? selectedDays : { from: undefined, to: undefined });
|
|
172
173
|
const [t] = useTranslation();
|
|
173
174
|
const locale = useLocale(language);
|
|
174
|
-
|
|
175
|
+
React.useEffect(() => {
|
|
175
176
|
if (selectedDays) {
|
|
176
177
|
setNewRange(selectedDays);
|
|
177
178
|
}
|
|
178
179
|
}, [selectedDays]);
|
|
179
|
-
const handleOnRangeSelect =
|
|
180
|
+
const handleOnRangeSelect = React.useCallback((range) => {
|
|
180
181
|
if (range && range.from) {
|
|
181
182
|
if (range.to === undefined) {
|
|
182
183
|
setNewRange({ from: range.from, to: range.from });
|
|
@@ -189,7 +190,7 @@ const DayRangePicker = ({ onRangeSelect, selectedDays, disabledDays, dataTestId,
|
|
|
189
190
|
setNewRange({ from: undefined, to: undefined });
|
|
190
191
|
}
|
|
191
192
|
}, []);
|
|
192
|
-
const handleCancel =
|
|
193
|
+
const handleCancel = React.useCallback(() => {
|
|
193
194
|
if (selectedDays) {
|
|
194
195
|
setNewRange(selectedDays);
|
|
195
196
|
}
|
|
@@ -198,7 +199,7 @@ const DayRangePicker = ({ onRangeSelect, selectedDays, disabledDays, dataTestId,
|
|
|
198
199
|
const clearSelectedDays = () => {
|
|
199
200
|
setNewRange({ from: undefined, to: undefined });
|
|
200
201
|
};
|
|
201
|
-
const handleApply =
|
|
202
|
+
const handleApply = React.useCallback(() => {
|
|
202
203
|
onRangeSelect && onRangeSelect(newRange);
|
|
203
204
|
onClose && onClose();
|
|
204
205
|
}, [onRangeSelect, newRange, onClose]);
|
|
@@ -221,7 +222,7 @@ const calcQuickOptions = (numberOfDays) => {
|
|
|
221
222
|
const QuickOptionButton = ({ option, onClick, timezone }) => {
|
|
222
223
|
const [t] = useTranslation();
|
|
223
224
|
const { nowDate, startOf, endOf, subtract } = reactDateAndTimeHooks.useDateAndTime();
|
|
224
|
-
const handleClick =
|
|
225
|
+
const handleClick = React.useCallback(() => {
|
|
225
226
|
const from = subtract(startOf(nowDate, "day"), option.includeExtraDay ? option.last : option.last - 1, option.unit);
|
|
226
227
|
onClick({
|
|
227
228
|
from,
|
|
@@ -281,7 +282,7 @@ const QuickOptionButton = ({ option, onClick, timezone }) => {
|
|
|
281
282
|
const DayRangePickerPopover = ({ interval = { from: undefined, to: undefined }, showQuickOptions, disabledDays, className, dataTestId, onRangeSelect, variant = "ghost-neutral", size = "small", placement, timezone, fullwidth, bindTo, buttonContent, max, disabled, }) => {
|
|
282
283
|
const { language } = reactCoreHooks.useCurrentUserLanguage();
|
|
283
284
|
const [t] = useTranslation();
|
|
284
|
-
const handleOnRangeSelect =
|
|
285
|
+
const handleOnRangeSelect = React.useCallback((range) => {
|
|
285
286
|
if (range) {
|
|
286
287
|
onRangeSelect(range);
|
|
287
288
|
}
|
|
@@ -289,6 +290,189 @@ const DayRangePickerPopover = ({ interval = { from: undefined, to: undefined },
|
|
|
289
290
|
return (jsxRuntime.jsxs(reactComponents.Popover, { placement: placement, children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.Button, { className: className, dataTestId: dataTestId !== null && dataTestId !== void 0 ? dataTestId : "show-date-range", disabled: disabled, fullWidth: fullwidth, size: size, suffix: jsxRuntime.jsx(reactComponents.Icon, { ariaLabel: t("dayRangePickerPopover.icon.tooltip.calendar"), name: "Calendar", size: "small" }), variant: variant, children: buttonContent }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: close => (jsxRuntime.jsx(reactComponents.Card, { className: "min-w-min p-4", dataTestId: "range-popover-list", children: jsxRuntime.jsx(DayRangePicker, { disabledDays: disabledDays, language: language || "en", max: max, onClose: close, onRangeSelect: handleOnRangeSelect, selectedDays: interval, showQuickOptions: showQuickOptions, timezone: timezone }) })) })] }));
|
|
290
291
|
};
|
|
291
292
|
|
|
293
|
+
const cvaTimelineElement = cssClassVarianceUtilities.cvaMerge([
|
|
294
|
+
"flex",
|
|
295
|
+
"group/timeline",
|
|
296
|
+
], {
|
|
297
|
+
variants: {
|
|
298
|
+
selected: {
|
|
299
|
+
true: "bg-blue-50 rounded-[4px]",
|
|
300
|
+
false: "",
|
|
301
|
+
},
|
|
302
|
+
hoverBehavior: {
|
|
303
|
+
true: "hover:bg-gray-50 hover:rounded-[4px]",
|
|
304
|
+
false: "",
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
const cvaDotWrapper = cssClassVarianceUtilities.cvaMerge([
|
|
309
|
+
"relative",
|
|
310
|
+
"flex",
|
|
311
|
+
"flex-col",
|
|
312
|
+
"items-center",
|
|
313
|
+
"justify-items-center",
|
|
314
|
+
"ml-2",
|
|
315
|
+
"mr-2",
|
|
316
|
+
"pt-2",
|
|
317
|
+
"min-w-6",
|
|
318
|
+
]);
|
|
319
|
+
const cvaLine = cssClassVarianceUtilities.cvaMerge([
|
|
320
|
+
"absolute",
|
|
321
|
+
"top-0",
|
|
322
|
+
"w-px",
|
|
323
|
+
"h-full",
|
|
324
|
+
"border-l",
|
|
325
|
+
"border-slate-300",
|
|
326
|
+
"group-first/timeline:top-3.5",
|
|
327
|
+
"group-last/timeline:h-4",
|
|
328
|
+
], {
|
|
329
|
+
variants: {
|
|
330
|
+
lineStyle: {
|
|
331
|
+
dashed: "border-dashed",
|
|
332
|
+
solid: ""
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
const cvaCustomDot = cssClassVarianceUtilities.cvaMerge([
|
|
337
|
+
"flex",
|
|
338
|
+
"items-center",
|
|
339
|
+
"justify-center",
|
|
340
|
+
"w-5",
|
|
341
|
+
"h-5",
|
|
342
|
+
"bg-white",
|
|
343
|
+
"rounded-full",
|
|
344
|
+
"border",
|
|
345
|
+
"border-white",
|
|
346
|
+
"z-[2]"
|
|
347
|
+
]);
|
|
348
|
+
const cvaToggleBtnIcon = cssClassVarianceUtilities.cvaMerge([
|
|
349
|
+
"mr-1",
|
|
350
|
+
"rounded-lg",
|
|
351
|
+
"transition-transform",
|
|
352
|
+
"duration-300",
|
|
353
|
+
"cursor-pointer",
|
|
354
|
+
], {
|
|
355
|
+
variants: {
|
|
356
|
+
rotated: {
|
|
357
|
+
true: "rotate-180",
|
|
358
|
+
false: "",
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
const cvaTimelineElementTime = cssClassVarianceUtilities.cvaMerge([
|
|
363
|
+
"min-h-6",
|
|
364
|
+
"content-center",
|
|
365
|
+
], {
|
|
366
|
+
variants: {
|
|
367
|
+
width: {
|
|
368
|
+
large: "min-w-16",
|
|
369
|
+
small: "min-w-10",
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The Timeline component offers a visual representation of events or milestones in chronological order, helping users easily follow the progression of activities, tasks, or data points over time.
|
|
376
|
+
*
|
|
377
|
+
* @param {TimelineProps} props - The props for the Timeline component
|
|
378
|
+
* @returns {JSX.Element} Timeline component
|
|
379
|
+
*/
|
|
380
|
+
const Timeline = ({ className, dataTestId, children, dateHeader, customHeader, toggleButton, }) => {
|
|
381
|
+
var _a;
|
|
382
|
+
const ref = React.useRef(null);
|
|
383
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
384
|
+
const { formatDate, formatRange } = reactDateAndTimeHooks.useDateAndTime();
|
|
385
|
+
const locale = reactDateAndTimeHooks.useLocale();
|
|
386
|
+
const hourCycle = dateAndTimeUtils.getHourCycle(locale);
|
|
387
|
+
const childrenArray = React.useMemo(() => React.Children.toArray(children), [children]);
|
|
388
|
+
const [firstChild, ...remainingChildren] = childrenArray;
|
|
389
|
+
const childProps = React.isValidElement(firstChild) ? firstChild.props : {};
|
|
390
|
+
const lastChild = remainingChildren.pop();
|
|
391
|
+
const middleChildren = remainingChildren;
|
|
392
|
+
const renderToggleButton = () => (jsxRuntime.jsxs("div", { className: cvaTimelineElement(), children: [(childProps === null || childProps === void 0 ? void 0 : childProps.date) ? (jsxRuntime.jsx("div", { className: cvaTimelineElementTime({
|
|
393
|
+
width: hourCycle === "h12" || hourCycle === "h11" ? "large" : "small",
|
|
394
|
+
}) })) : null, jsxRuntime.jsx("div", { className: cvaDotWrapper(), children: jsxRuntime.jsx("div", { className: cvaLine({ lineStyle: childProps === null || childProps === void 0 ? void 0 : childProps.lineStyle }) }) }), jsxRuntime.jsxs("div", { className: "flex text-sm text-secondary-500 cursor-pointer items-center min-h-8 hover:text-secondary-600", "data-testid": "timeline-toggle-btn", onClick: () => setIsOpen(!isOpen), children: [jsxRuntime.jsx(reactComponents.Icon, { className: cvaToggleBtnIcon({ rotated: isOpen }), name: "ChevronDown", size: "small" }), jsxRuntime.jsx("span", { children: isOpen ? toggleButton === null || toggleButton === void 0 ? void 0 : toggleButton.collapsedLabel : toggleButton === null || toggleButton === void 0 ? void 0 : toggleButton.expandedLabel })] })] }));
|
|
395
|
+
const renderDateHeader = () => {
|
|
396
|
+
if (!dateHeader) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
if (customHeader) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
const { date, dateRange, showTime } = dateHeader;
|
|
403
|
+
return (jsxRuntime.jsx("div", { className: "pb-4", "data-testid": "timeline-date-header", children: date ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.Text, { size: "medium", type: "span", weight: "thick", children: formatDate(date, { selectFormat: !showTime ? "dateOnly" : undefined }) }), jsxRuntime.jsx(reactComponents.Text, { className: "text-secondary-500", size: "medium", type: "span", weight: "thick", children: ` (${dateAndTimeUtils.timeSinceAuto(date, new Date())})` })] })) : (jsxRuntime.jsx(reactComponents.Text, { className: "pr-1", size: "medium", type: "span", weight: "thick", children: formatRange(dateRange, { dateStyle: !showTime ? "medium" : undefined }) })) }));
|
|
404
|
+
};
|
|
405
|
+
return (jsxRuntime.jsxs("div", { className: className, "data-testid": dataTestId, children: [renderDateHeader(), customHeader ? jsxRuntime.jsx("div", { className: "pb-4 font-semibold text-sm", children: customHeader }) : null, toggleButton && middleChildren.length > 0 ? (jsxRuntime.jsxs("div", { children: [firstChild, middleChildren.length > 0 ? renderToggleButton() : null, jsxRuntime.jsxs("div", { "aria-hidden": !isOpen, className: "transition-all duration-300 overflow-hidden", ref: ref, style: { height: isOpen ? `${(_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollHeight}px` : "0px" }, children: [jsxRuntime.jsx("div", {}), middleChildren, jsxRuntime.jsx("div", {})] }), lastChild] })) : (jsxRuntime.jsx("div", { children: children }))] }));
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* TimelineElement - entry on a timeline - rendered with an icon on the timeline
|
|
410
|
+
* - a date as the header and the children provided as body.
|
|
411
|
+
*
|
|
412
|
+
* @param {TimeLineElementProps} props the props
|
|
413
|
+
* @returns {JSX.Element} component
|
|
414
|
+
*/
|
|
415
|
+
const TimelineElement = ({ date, children, className, dataTestId = "timeline-element", header, onClick, actionButton, selected, customDot, lineStyle = "solid", hoverBehavior = false, }) => {
|
|
416
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
417
|
+
const { formatDate } = reactDateAndTimeHooks.useDateAndTime();
|
|
418
|
+
const locale = reactDateAndTimeHooks.useLocale();
|
|
419
|
+
const formattedDate = date
|
|
420
|
+
? formatDate(date, { selectFormat: "timeOnly", timeFormat: "short" })
|
|
421
|
+
: null;
|
|
422
|
+
const handleMouseEnter = () => {
|
|
423
|
+
if (hoverBehavior) {
|
|
424
|
+
setIsHovered(true);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const handleMouseLeave = () => {
|
|
428
|
+
if (hoverBehavior) {
|
|
429
|
+
setIsHovered(false);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return (jsxRuntime.jsxs("div", { className: cvaTimelineElement({ className, selected, hoverBehavior }), "data-date": date, "data-testid": dataTestId, onClick: onClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [renderDate(formattedDate, locale), jsxRuntime.jsxs("div", { className: cvaDotWrapper(), children: [renderDot(customDot, isHovered, selected), jsxRuntime.jsx("div", { className: cvaLine({ lineStyle }) })] }), jsxRuntime.jsxs("div", { className: "pt-2 pb-2", children: [renderHeader(header), renderChildren(children), renderActionButton(actionButton)] })] }));
|
|
433
|
+
};
|
|
434
|
+
const renderDate = (formattedDate, locale) => {
|
|
435
|
+
if (!formattedDate) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
return (jsxRuntime.jsx("div", { className: "pt-2 items-center", children: timelineElementTime(formattedDate, locale) }));
|
|
439
|
+
};
|
|
440
|
+
const renderDot = (customDot, isHovered, selected) => {
|
|
441
|
+
return (customDot === null || customDot === void 0 ? void 0 : customDot.name)
|
|
442
|
+
? customDotElement(customDot.name, isHovered, selected, customDot.color)
|
|
443
|
+
: defaultDot(isHovered, selected);
|
|
444
|
+
};
|
|
445
|
+
const renderHeader = (header) => {
|
|
446
|
+
if (!header) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
return jsxRuntime.jsx("div", { className: "text-sm mt-0.5 content-center font-medium", children: header });
|
|
450
|
+
};
|
|
451
|
+
const renderChildren = (children) => {
|
|
452
|
+
if (!children) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
return jsxRuntime.jsx("div", { className: "text-secondary-500 mt-0.5 content-center text-sm", children: children });
|
|
456
|
+
};
|
|
457
|
+
const renderActionButton = (actionButton) => {
|
|
458
|
+
if (!actionButton) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
return (jsxRuntime.jsx("div", { className: "pt-2", children: jsxRuntime.jsx(reactComponents.Button, { prefix: jsxRuntime.jsx(reactComponents.Icon, { name: actionButton.iconName, size: "small" }), size: "small", variant: "secondary", children: actionButton.label }) }));
|
|
462
|
+
};
|
|
463
|
+
const timelineElementTime = (formattedDate, locale) => {
|
|
464
|
+
const hourCycle = dateAndTimeUtils.getHourCycle(locale);
|
|
465
|
+
return (jsxRuntime.jsx(reactComponents.Text, { align: "right", className: cvaTimelineElementTime({
|
|
466
|
+
width: hourCycle === "h12" || hourCycle === "h11" ? "large" : "small"
|
|
467
|
+
}), size: "small", subtle: true, children: formattedDate }));
|
|
468
|
+
};
|
|
469
|
+
const CircleIcon = ({ selected, hovered }) => (jsxRuntime.jsxs("svg", { className: "relative z-[2]", "data-testid": "timeline-circle-icon", height: "24", viewBox: "0 0 24 24", width: "24", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", fill: selected ? "#EFF6FF" : hovered ? "#F9FAFB" : "white", r: "10" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", fill: "#2563EB", r: "8" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", fill: "white", r: "3" })] }));
|
|
470
|
+
const DotIcon = () => (jsxRuntime.jsxs("svg", { className: "relative z-[2]", "data-testid": "timeline-dot-icon", height: "24", viewBox: "0 0 24 24", width: "24", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", fill: "white", r: "6" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", fill: "#2563EB", r: "4" })] }));
|
|
471
|
+
const defaultDot = (hovered, selected) => (hovered || selected) ? jsxRuntime.jsx(CircleIcon, { hovered: hovered, selected: selected }) : jsxRuntime.jsx(DotIcon, {});
|
|
472
|
+
const customDotElement = (name, hovered, selected, color) => (hovered || selected
|
|
473
|
+
? jsxRuntime.jsx(CircleIcon, { hovered: hovered, selected: selected })
|
|
474
|
+
: (jsxRuntime.jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: jsxRuntime.jsx("div", { className: cvaCustomDot(), children: jsxRuntime.jsx(reactComponents.Icon, { className: "block", color: color, dataTestId: "timeline-custom-dot", name: name, size: "small" }) }) })));
|
|
475
|
+
|
|
292
476
|
/*
|
|
293
477
|
* ----------------------------
|
|
294
478
|
* | SETUP TRANSLATIONS START |
|
|
@@ -304,3 +488,5 @@ exports.DayPicker = DayPicker;
|
|
|
304
488
|
exports.DayRangePicker = DayRangePicker;
|
|
305
489
|
exports.DayRangePickerPopover = DayRangePickerPopover;
|
|
306
490
|
exports.MS_PER_HOUR = MS_PER_HOUR;
|
|
491
|
+
exports.Timeline = Timeline;
|
|
492
|
+
exports.TimelineElement = TimelineElement;
|
package/index.esm.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
|
|
3
|
-
import { toDateUtil, formatDateUtil, isEqualUtil, timeSinceAuto } from '@trackunit/date-and-time-utils';
|
|
3
|
+
import { toDateUtil, formatDateUtil, isEqualUtil, timeSinceAuto, getHourCycle } from '@trackunit/date-and-time-utils';
|
|
4
4
|
import { useLocale as useLocale$1, useDateAndTime } from '@trackunit/react-date-and-time-hooks';
|
|
5
5
|
import { color } from '@trackunit/ui-design-tokens';
|
|
6
|
-
import { useMemo, useCallback, useState, useEffect } from 'react';
|
|
7
|
-
import { Tooltip, IconButton, Icon, Button, Popover, PopoverTrigger, PopoverContent, Card } from '@trackunit/react-components';
|
|
6
|
+
import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react';
|
|
7
|
+
import { Tooltip, IconButton, Icon, Button, Popover, PopoverTrigger, PopoverContent, Card, Text } from '@trackunit/react-components';
|
|
8
8
|
import { DateTimeFormat } from '@trackunit/shared-utils';
|
|
9
9
|
import { DayPicker as DayPicker$1 } from 'react-day-picker';
|
|
10
10
|
import { twMerge } from 'tailwind-merge';
|
|
11
11
|
import { enGB, enUS, da, de, cs, nl, fr, fi, hu, it, nb, pl, pt, ru, ro, es, sv, ja, th } from 'date-fns/locale';
|
|
12
12
|
import { useCurrentUserLanguage } from '@trackunit/react-core-hooks';
|
|
13
|
+
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
13
14
|
|
|
14
15
|
var defaultTranslations = {
|
|
15
16
|
"dateTime.instant.now": "Now",
|
|
@@ -287,6 +288,189 @@ const DayRangePickerPopover = ({ interval = { from: undefined, to: undefined },
|
|
|
287
288
|
return (jsxs(Popover, { placement: placement, children: [jsx(PopoverTrigger, { children: jsx(Button, { className: className, dataTestId: dataTestId !== null && dataTestId !== void 0 ? dataTestId : "show-date-range", disabled: disabled, fullWidth: fullwidth, size: size, suffix: jsx(Icon, { ariaLabel: t("dayRangePickerPopover.icon.tooltip.calendar"), name: "Calendar", size: "small" }), variant: variant, children: buttonContent }) }), jsx(PopoverContent, { children: close => (jsx(Card, { className: "min-w-min p-4", dataTestId: "range-popover-list", children: jsx(DayRangePicker, { disabledDays: disabledDays, language: language || "en", max: max, onClose: close, onRangeSelect: handleOnRangeSelect, selectedDays: interval, showQuickOptions: showQuickOptions, timezone: timezone }) })) })] }));
|
|
288
289
|
};
|
|
289
290
|
|
|
291
|
+
const cvaTimelineElement = cvaMerge([
|
|
292
|
+
"flex",
|
|
293
|
+
"group/timeline",
|
|
294
|
+
], {
|
|
295
|
+
variants: {
|
|
296
|
+
selected: {
|
|
297
|
+
true: "bg-blue-50 rounded-[4px]",
|
|
298
|
+
false: "",
|
|
299
|
+
},
|
|
300
|
+
hoverBehavior: {
|
|
301
|
+
true: "hover:bg-gray-50 hover:rounded-[4px]",
|
|
302
|
+
false: "",
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const cvaDotWrapper = cvaMerge([
|
|
307
|
+
"relative",
|
|
308
|
+
"flex",
|
|
309
|
+
"flex-col",
|
|
310
|
+
"items-center",
|
|
311
|
+
"justify-items-center",
|
|
312
|
+
"ml-2",
|
|
313
|
+
"mr-2",
|
|
314
|
+
"pt-2",
|
|
315
|
+
"min-w-6",
|
|
316
|
+
]);
|
|
317
|
+
const cvaLine = cvaMerge([
|
|
318
|
+
"absolute",
|
|
319
|
+
"top-0",
|
|
320
|
+
"w-px",
|
|
321
|
+
"h-full",
|
|
322
|
+
"border-l",
|
|
323
|
+
"border-slate-300",
|
|
324
|
+
"group-first/timeline:top-3.5",
|
|
325
|
+
"group-last/timeline:h-4",
|
|
326
|
+
], {
|
|
327
|
+
variants: {
|
|
328
|
+
lineStyle: {
|
|
329
|
+
dashed: "border-dashed",
|
|
330
|
+
solid: ""
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
const cvaCustomDot = cvaMerge([
|
|
335
|
+
"flex",
|
|
336
|
+
"items-center",
|
|
337
|
+
"justify-center",
|
|
338
|
+
"w-5",
|
|
339
|
+
"h-5",
|
|
340
|
+
"bg-white",
|
|
341
|
+
"rounded-full",
|
|
342
|
+
"border",
|
|
343
|
+
"border-white",
|
|
344
|
+
"z-[2]"
|
|
345
|
+
]);
|
|
346
|
+
const cvaToggleBtnIcon = cvaMerge([
|
|
347
|
+
"mr-1",
|
|
348
|
+
"rounded-lg",
|
|
349
|
+
"transition-transform",
|
|
350
|
+
"duration-300",
|
|
351
|
+
"cursor-pointer",
|
|
352
|
+
], {
|
|
353
|
+
variants: {
|
|
354
|
+
rotated: {
|
|
355
|
+
true: "rotate-180",
|
|
356
|
+
false: "",
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
const cvaTimelineElementTime = cvaMerge([
|
|
361
|
+
"min-h-6",
|
|
362
|
+
"content-center",
|
|
363
|
+
], {
|
|
364
|
+
variants: {
|
|
365
|
+
width: {
|
|
366
|
+
large: "min-w-16",
|
|
367
|
+
small: "min-w-10",
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* The Timeline component offers a visual representation of events or milestones in chronological order, helping users easily follow the progression of activities, tasks, or data points over time.
|
|
374
|
+
*
|
|
375
|
+
* @param {TimelineProps} props - The props for the Timeline component
|
|
376
|
+
* @returns {JSX.Element} Timeline component
|
|
377
|
+
*/
|
|
378
|
+
const Timeline = ({ className, dataTestId, children, dateHeader, customHeader, toggleButton, }) => {
|
|
379
|
+
var _a;
|
|
380
|
+
const ref = useRef(null);
|
|
381
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
382
|
+
const { formatDate, formatRange } = useDateAndTime();
|
|
383
|
+
const locale = useLocale$1();
|
|
384
|
+
const hourCycle = getHourCycle(locale);
|
|
385
|
+
const childrenArray = useMemo(() => React.Children.toArray(children), [children]);
|
|
386
|
+
const [firstChild, ...remainingChildren] = childrenArray;
|
|
387
|
+
const childProps = React.isValidElement(firstChild) ? firstChild.props : {};
|
|
388
|
+
const lastChild = remainingChildren.pop();
|
|
389
|
+
const middleChildren = remainingChildren;
|
|
390
|
+
const renderToggleButton = () => (jsxs("div", { className: cvaTimelineElement(), children: [(childProps === null || childProps === void 0 ? void 0 : childProps.date) ? (jsx("div", { className: cvaTimelineElementTime({
|
|
391
|
+
width: hourCycle === "h12" || hourCycle === "h11" ? "large" : "small",
|
|
392
|
+
}) })) : null, jsx("div", { className: cvaDotWrapper(), children: jsx("div", { className: cvaLine({ lineStyle: childProps === null || childProps === void 0 ? void 0 : childProps.lineStyle }) }) }), jsxs("div", { className: "flex text-sm text-secondary-500 cursor-pointer items-center min-h-8 hover:text-secondary-600", "data-testid": "timeline-toggle-btn", onClick: () => setIsOpen(!isOpen), children: [jsx(Icon, { className: cvaToggleBtnIcon({ rotated: isOpen }), name: "ChevronDown", size: "small" }), jsx("span", { children: isOpen ? toggleButton === null || toggleButton === void 0 ? void 0 : toggleButton.collapsedLabel : toggleButton === null || toggleButton === void 0 ? void 0 : toggleButton.expandedLabel })] })] }));
|
|
393
|
+
const renderDateHeader = () => {
|
|
394
|
+
if (!dateHeader) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
if (customHeader) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
const { date, dateRange, showTime } = dateHeader;
|
|
401
|
+
return (jsx("div", { className: "pb-4", "data-testid": "timeline-date-header", children: date ? (jsxs(Fragment, { children: [jsx(Text, { size: "medium", type: "span", weight: "thick", children: formatDate(date, { selectFormat: !showTime ? "dateOnly" : undefined }) }), jsx(Text, { className: "text-secondary-500", size: "medium", type: "span", weight: "thick", children: ` (${timeSinceAuto(date, new Date())})` })] })) : (jsx(Text, { className: "pr-1", size: "medium", type: "span", weight: "thick", children: formatRange(dateRange, { dateStyle: !showTime ? "medium" : undefined }) })) }));
|
|
402
|
+
};
|
|
403
|
+
return (jsxs("div", { className: className, "data-testid": dataTestId, children: [renderDateHeader(), customHeader ? jsx("div", { className: "pb-4 font-semibold text-sm", children: customHeader }) : null, toggleButton && middleChildren.length > 0 ? (jsxs("div", { children: [firstChild, middleChildren.length > 0 ? renderToggleButton() : null, jsxs("div", { "aria-hidden": !isOpen, className: "transition-all duration-300 overflow-hidden", ref: ref, style: { height: isOpen ? `${(_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollHeight}px` : "0px" }, children: [jsx("div", {}), middleChildren, jsx("div", {})] }), lastChild] })) : (jsx("div", { children: children }))] }));
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* TimelineElement - entry on a timeline - rendered with an icon on the timeline
|
|
408
|
+
* - a date as the header and the children provided as body.
|
|
409
|
+
*
|
|
410
|
+
* @param {TimeLineElementProps} props the props
|
|
411
|
+
* @returns {JSX.Element} component
|
|
412
|
+
*/
|
|
413
|
+
const TimelineElement = ({ date, children, className, dataTestId = "timeline-element", header, onClick, actionButton, selected, customDot, lineStyle = "solid", hoverBehavior = false, }) => {
|
|
414
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
415
|
+
const { formatDate } = useDateAndTime();
|
|
416
|
+
const locale = useLocale$1();
|
|
417
|
+
const formattedDate = date
|
|
418
|
+
? formatDate(date, { selectFormat: "timeOnly", timeFormat: "short" })
|
|
419
|
+
: null;
|
|
420
|
+
const handleMouseEnter = () => {
|
|
421
|
+
if (hoverBehavior) {
|
|
422
|
+
setIsHovered(true);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
const handleMouseLeave = () => {
|
|
426
|
+
if (hoverBehavior) {
|
|
427
|
+
setIsHovered(false);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
return (jsxs("div", { className: cvaTimelineElement({ className, selected, hoverBehavior }), "data-date": date, "data-testid": dataTestId, onClick: onClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [renderDate(formattedDate, locale), jsxs("div", { className: cvaDotWrapper(), children: [renderDot(customDot, isHovered, selected), jsx("div", { className: cvaLine({ lineStyle }) })] }), jsxs("div", { className: "pt-2 pb-2", children: [renderHeader(header), renderChildren(children), renderActionButton(actionButton)] })] }));
|
|
431
|
+
};
|
|
432
|
+
const renderDate = (formattedDate, locale) => {
|
|
433
|
+
if (!formattedDate) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
return (jsx("div", { className: "pt-2 items-center", children: timelineElementTime(formattedDate, locale) }));
|
|
437
|
+
};
|
|
438
|
+
const renderDot = (customDot, isHovered, selected) => {
|
|
439
|
+
return (customDot === null || customDot === void 0 ? void 0 : customDot.name)
|
|
440
|
+
? customDotElement(customDot.name, isHovered, selected, customDot.color)
|
|
441
|
+
: defaultDot(isHovered, selected);
|
|
442
|
+
};
|
|
443
|
+
const renderHeader = (header) => {
|
|
444
|
+
if (!header) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
return jsx("div", { className: "text-sm mt-0.5 content-center font-medium", children: header });
|
|
448
|
+
};
|
|
449
|
+
const renderChildren = (children) => {
|
|
450
|
+
if (!children) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
return jsx("div", { className: "text-secondary-500 mt-0.5 content-center text-sm", children: children });
|
|
454
|
+
};
|
|
455
|
+
const renderActionButton = (actionButton) => {
|
|
456
|
+
if (!actionButton) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
return (jsx("div", { className: "pt-2", children: jsx(Button, { prefix: jsx(Icon, { name: actionButton.iconName, size: "small" }), size: "small", variant: "secondary", children: actionButton.label }) }));
|
|
460
|
+
};
|
|
461
|
+
const timelineElementTime = (formattedDate, locale) => {
|
|
462
|
+
const hourCycle = getHourCycle(locale);
|
|
463
|
+
return (jsx(Text, { align: "right", className: cvaTimelineElementTime({
|
|
464
|
+
width: hourCycle === "h12" || hourCycle === "h11" ? "large" : "small"
|
|
465
|
+
}), size: "small", subtle: true, children: formattedDate }));
|
|
466
|
+
};
|
|
467
|
+
const CircleIcon = ({ selected, hovered }) => (jsxs("svg", { className: "relative z-[2]", "data-testid": "timeline-circle-icon", height: "24", viewBox: "0 0 24 24", width: "24", children: [jsx("circle", { cx: "12", cy: "12", fill: selected ? "#EFF6FF" : hovered ? "#F9FAFB" : "white", r: "10" }), jsx("circle", { cx: "12", cy: "12", fill: "#2563EB", r: "8" }), jsx("circle", { cx: "12", cy: "12", fill: "white", r: "3" })] }));
|
|
468
|
+
const DotIcon = () => (jsxs("svg", { className: "relative z-[2]", "data-testid": "timeline-dot-icon", height: "24", viewBox: "0 0 24 24", width: "24", children: [jsx("circle", { cx: "12", cy: "12", fill: "white", r: "6" }), jsx("circle", { cx: "12", cy: "12", fill: "#2563EB", r: "4" })] }));
|
|
469
|
+
const defaultDot = (hovered, selected) => (hovered || selected) ? jsx(CircleIcon, { hovered: hovered, selected: selected }) : jsx(DotIcon, {});
|
|
470
|
+
const customDotElement = (name, hovered, selected, color) => (hovered || selected
|
|
471
|
+
? jsx(CircleIcon, { hovered: hovered, selected: selected })
|
|
472
|
+
: (jsx("div", { className: "w-6 h-6 flex items-center justify-center", children: jsx("div", { className: cvaCustomDot(), children: jsx(Icon, { className: "block", color: color, dataTestId: "timeline-custom-dot", name: name, size: "small" }) }) })));
|
|
473
|
+
|
|
290
474
|
/*
|
|
291
475
|
* ----------------------------
|
|
292
476
|
* | SETUP TRANSLATIONS START |
|
|
@@ -296,4 +480,4 @@ const DayRangePickerPopover = ({ interval = { from: undefined, to: undefined },
|
|
|
296
480
|
*/
|
|
297
481
|
setupLibraryTranslations();
|
|
298
482
|
|
|
299
|
-
export { DateTime, DateTimeHumanized, DayPicker, DayRangePicker, DayRangePickerPopover, MS_PER_HOUR };
|
|
483
|
+
export { DateTime, DateTimeHumanized, DayPicker, DayRangePicker, DayRangePickerPopover, MS_PER_HOUR, Timeline, TimelineElement };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-date-and-time-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.204",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"@trackunit/date-and-time-utils": "*",
|
|
18
18
|
"@trackunit/ui-design-tokens": "*",
|
|
19
19
|
"@trackunit/shared-utils": "*",
|
|
20
|
-
"@trackunit/i18n-library-translation": "*"
|
|
20
|
+
"@trackunit/i18n-library-translation": "*",
|
|
21
|
+
"@trackunit/ui-icons": "*",
|
|
22
|
+
"@trackunit/css-class-variance-utilities": "*"
|
|
21
23
|
},
|
|
22
24
|
"module": "./index.esm.js",
|
|
23
25
|
"main": "./index.cjs.js",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DateRange } from "@trackunit/date-and-time-utils";
|
|
2
|
+
import { CommonProps } from "@trackunit/react-components";
|
|
3
|
+
import React from "react";
|
|
4
|
+
export interface TimelineProps extends CommonProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
toggleButton?: {
|
|
7
|
+
expandedLabel: string;
|
|
8
|
+
collapsedLabel: string;
|
|
9
|
+
};
|
|
10
|
+
dateHeader?: ({
|
|
11
|
+
date: Date;
|
|
12
|
+
dateRange?: never;
|
|
13
|
+
} | {
|
|
14
|
+
date?: never;
|
|
15
|
+
dateRange: DateRange;
|
|
16
|
+
}) & {
|
|
17
|
+
showTime?: boolean;
|
|
18
|
+
};
|
|
19
|
+
customHeader?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The Timeline component offers a visual representation of events or milestones in chronological order, helping users easily follow the progression of activities, tasks, or data points over time.
|
|
23
|
+
*
|
|
24
|
+
* @param {TimelineProps} props - The props for the Timeline component
|
|
25
|
+
* @returns {JSX.Element} Timeline component
|
|
26
|
+
*/
|
|
27
|
+
export declare const Timeline: ({ className, dataTestId, children, dateHeader, customHeader, toggleButton, }: TimelineProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Timeline } from "./Timeline";
|
|
3
|
+
type Story = StoryObj<typeof Timeline>;
|
|
4
|
+
declare const meta: Meta<typeof Timeline>;
|
|
5
|
+
export default meta;
|
|
6
|
+
export declare const packageName: () => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare const Default: Story;
|
|
8
|
+
export declare const Usage: () => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export declare const CustomHeader: () => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare const CustomTimestamp: () => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare const CustomDot: () => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare const IncorrectUse: () => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare const HoverAndSelectBehavior: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const cvaTimelineElement: (props?: ({
|
|
2
|
+
selected?: boolean | null | undefined;
|
|
3
|
+
hoverBehavior?: boolean | null | undefined;
|
|
4
|
+
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
5
|
+
export declare const cvaDotWrapper: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
6
|
+
export declare const cvaLine: (props?: ({
|
|
7
|
+
lineStyle?: "dashed" | "solid" | null | undefined;
|
|
8
|
+
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
9
|
+
export declare const cvaCustomDot: (props?: import("class-variance-authority/dist/types").ClassProp | undefined) => string;
|
|
10
|
+
export declare const cvaToggleBtnIcon: (props?: ({
|
|
11
|
+
rotated?: boolean | null | undefined;
|
|
12
|
+
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
13
|
+
export declare const cvaTimelineElementTime: (props?: ({
|
|
14
|
+
width?: "large" | "small" | null | undefined;
|
|
15
|
+
} & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { CommonProps, IconColors } from "@trackunit/react-components";
|
|
3
|
+
import { IconName } from "@trackunit/ui-icons";
|
|
4
|
+
interface TimeLineElementProps extends CommonProps {
|
|
5
|
+
date?: Date;
|
|
6
|
+
header?: React.ReactNode;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
customDot?: {
|
|
9
|
+
name: IconName;
|
|
10
|
+
color: IconColors;
|
|
11
|
+
};
|
|
12
|
+
onClick?: () => void;
|
|
13
|
+
actionButton?: {
|
|
14
|
+
label: string;
|
|
15
|
+
iconName: IconName;
|
|
16
|
+
};
|
|
17
|
+
selected?: boolean;
|
|
18
|
+
lineStyle?: "solid" | "dashed";
|
|
19
|
+
hoverBehavior?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* TimelineElement - entry on a timeline - rendered with an icon on the timeline
|
|
23
|
+
* - a date as the header and the children provided as body.
|
|
24
|
+
*
|
|
25
|
+
* @param {TimeLineElementProps} props the props
|
|
26
|
+
* @returns {JSX.Element} component
|
|
27
|
+
*/
|
|
28
|
+
export declare const TimelineElement: ({ date, children, className, dataTestId, header, onClick, actionButton, selected, customDot, lineStyle, hoverBehavior, }: TimeLineElementProps) => import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export {};
|
package/src/index.d.ts
CHANGED