@kkkarsss/ui 1.2.1 → 1.2.2
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.
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ExternalLink } from 'lucide-react';
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
2
|
import { jc } from '../../../utils';
|
|
4
3
|
import { Flex } from '../../layout';
|
|
5
|
-
|
|
6
|
-
export const CalendarItem = ({ title, isCompleted, isInWork, onClick, icon, color, className, }) => {
|
|
4
|
+
export const CalendarItem = ({ title, isCompleted, isInWork, icon, color, className }) => {
|
|
7
5
|
return (_jsx("div", { className: jc('w-full h-full rounded-[8px] border flex flex-col transition-colors relative', !color && (isCompleted ? 'border-secondary bg-accent' : 'border-accent bg-accent'), className), style: {
|
|
8
6
|
backgroundColor: color ? color : undefined,
|
|
9
7
|
borderColor: color ? color : undefined,
|
|
10
|
-
}, children:
|
|
11
|
-
e.stopPropagation();
|
|
12
|
-
onClick();
|
|
13
|
-
}, className: "cursor-pointer hover:text-primary transition-colors pr-l", children: _jsx(ExternalLink, { size: 14 }) }))] }) }));
|
|
8
|
+
}, children: _jsx(Flex, { gap: "8px", align: "center", justify: "space-between", type: "fill", children: _jsxs(Flex, { gap: "8px", align: "center", type: "fill", className: "min-w-0 flex-1", children: [title, isInWork && icon] }) }) }));
|
|
14
9
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { CalendarItemWrapper } from './calendar-item-wrapper';
|
|
4
4
|
import { CalendarSlot } from './calendar-slot';
|
|
5
5
|
import { calculateTaskPositions, groupTasksBySlot } from './utils';
|
|
@@ -8,10 +8,55 @@ export * from './types';
|
|
|
8
8
|
export * from './calendar-item-wrapper';
|
|
9
9
|
export * from './calendar-item';
|
|
10
10
|
const DEFAULT_SLOTS = Array.from({ length: 96 }, (_, i) => i);
|
|
11
|
-
|
|
11
|
+
const CurrentTimeLine = () => {
|
|
12
|
+
const [now, setNow] = useState(new Date());
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const interval = setInterval(() => {
|
|
15
|
+
setNow(new Date());
|
|
16
|
+
}, 60000);
|
|
17
|
+
return () => clearInterval(interval);
|
|
18
|
+
}, []);
|
|
19
|
+
const topOffset = useMemo(() => {
|
|
20
|
+
const hours = now.getHours();
|
|
21
|
+
const minutes = now.getMinutes();
|
|
22
|
+
const totalMinutes = hours * 60 + minutes;
|
|
23
|
+
// 15 минут = 20 пикселей (высота слота)
|
|
24
|
+
return totalMinutes * (20 / 15);
|
|
25
|
+
}, [now]);
|
|
26
|
+
return (_jsxs("div", { className: "absolute left-0 right-0 z-50 pointer-events-none flex items-center", style: { top: `${topOffset}px` }, children: [_jsx("div", { className: "w-2 h-2 rounded-full bg-red-500 -ml-1" }), _jsx("div", { className: "flex-1 h-[2px] bg-red-500" })] }));
|
|
27
|
+
};
|
|
28
|
+
export const CalendarLike = ({ tasks, slots = DEFAULT_SLOTS, showCurrentTime = true, autoScrollToCurrentTime = true, renderTask, onTaskDrop, onTaskResize, onCreateTask, }) => {
|
|
29
|
+
const containerRef = useRef(null);
|
|
12
30
|
const [isDragging, setIsDragging] = useState(false);
|
|
13
31
|
const tasksWithPosition = useMemo(() => calculateTaskPositions(tasks), [tasks]);
|
|
14
32
|
const tasksBySlot = useMemo(() => groupTasksBySlot(tasksWithPosition), [tasksWithPosition]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (showCurrentTime && autoScrollToCurrentTime && containerRef.current) {
|
|
35
|
+
const now = new Date();
|
|
36
|
+
const hours = now.getHours();
|
|
37
|
+
const minutes = now.getMinutes();
|
|
38
|
+
const totalMinutes = hours * 60 + minutes;
|
|
39
|
+
// 15 минут = 20 пикселей (высота слота)
|
|
40
|
+
const topOffset = totalMinutes * (20 / 15);
|
|
41
|
+
// Находим ближайший скроллируемый родитель или используем окно
|
|
42
|
+
const scrollParent = (node) => {
|
|
43
|
+
if (!node)
|
|
44
|
+
return null;
|
|
45
|
+
const style = window.getComputedStyle(node);
|
|
46
|
+
if (/(auto|scroll)/.test(style.overflow + style.overflowY))
|
|
47
|
+
return node;
|
|
48
|
+
return scrollParent(node.parentElement);
|
|
49
|
+
};
|
|
50
|
+
const parent = scrollParent(containerRef.current);
|
|
51
|
+
if (parent) {
|
|
52
|
+
const containerTop = containerRef.current.getBoundingClientRect().top + parent.scrollTop;
|
|
53
|
+
parent.scrollTo({
|
|
54
|
+
top: containerTop + topOffset - parent.clientHeight / 2,
|
|
55
|
+
behavior: 'smooth',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}, [showCurrentTime, autoScrollToCurrentTime]);
|
|
15
60
|
const handleDragStart = (e, task) => {
|
|
16
61
|
setIsDragging(true);
|
|
17
62
|
e.dataTransfer.setData('taskId', task.id);
|
|
@@ -40,8 +85,8 @@ export const CalendarLike = ({ tasks, slots = DEFAULT_SLOTS, renderTask, onTaskD
|
|
|
40
85
|
onTaskDrop(taskId, hour, minutes);
|
|
41
86
|
}
|
|
42
87
|
};
|
|
43
|
-
return (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
88
|
+
return (_jsxs("div", { ref: containerRef, className: jc('relative border-l border-secondary/20 ml-12 select-none', isDragging ? 'is-dragging' : ''), children: [showCurrentTime && _jsx(CurrentTimeLine, {}), slots.map((slotIndex) => {
|
|
89
|
+
const tasksForSlot = tasksBySlot[slotIndex];
|
|
90
|
+
return (_jsx(CalendarSlot, { slotIndex: slotIndex, onDrop: handleDrop, onCreateTask: onCreateTask, children: tasksForSlot?.map((task) => (_jsx(CalendarItemWrapper, { task: task, position: task.position, onDragStart: (e) => handleDragStart(e, task), onDragEnd: handleDragEnd, onResize: onTaskResize, renderTask: renderTask }, task.id))) }, slotIndex));
|
|
91
|
+
})] }));
|
|
47
92
|
};
|
|
@@ -17,6 +17,8 @@ export type TCalendarLikeProps = {
|
|
|
17
17
|
tasks: TCalendarTask[];
|
|
18
18
|
hours?: number[];
|
|
19
19
|
slots?: number[];
|
|
20
|
+
showCurrentTime?: boolean;
|
|
21
|
+
autoScrollToCurrentTime?: boolean;
|
|
20
22
|
renderTask: (task: TCalendarTask) => ReactNode;
|
|
21
23
|
onTaskDrop?: (taskId: string, hour: number, minutes: number) => void;
|
|
22
24
|
onTaskResize?: (taskId: string, newEstimatedTime: number) => void;
|