@kkkarsss/ui 1.2.0 → 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.
- package/dist/ui/information/calendar-like/calendar-item.d.ts +1 -3
- package/dist/ui/information/calendar-like/calendar-item.js +5 -10
- package/dist/ui/information/calendar-like/calendar-like.js +52 -7
- package/dist/ui/information/calendar-like/calendar-like.stories.js +17 -2
- package/dist/ui/information/calendar-like/types.d.ts +2 -0
- package/dist/ui/information/cell/cell.d.ts +1 -1
- package/dist/ui/layout/icon-action/icon-action.d.ts +2 -2
- package/dist/ui/layout/icon-action/icon-action.js +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { type FC, type ReactNode } from 'react';
|
|
2
2
|
export type TCalendarItemProps = {
|
|
3
|
-
title:
|
|
4
|
-
description?: string;
|
|
3
|
+
title: ReactNode;
|
|
5
4
|
isCompleted?: boolean;
|
|
6
5
|
isInWork?: boolean;
|
|
7
6
|
onClick?: () => void;
|
|
8
7
|
icon?: ReactNode;
|
|
9
|
-
estimatedTime?: number;
|
|
10
8
|
color?: string;
|
|
11
9
|
className?: string;
|
|
12
10
|
};
|
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
backgroundColor: color ? `${color}80` : undefined,
|
|
4
|
+
export const CalendarItem = ({ title, isCompleted, isInWork, icon, color, className }) => {
|
|
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: {
|
|
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 p-0.5", children: _jsx(ExternalLink, { size: 14 }) }))] }), description && estimatedTime > 30 && (_jsx("div", { className: "truncate", children: _jsx(Typo, { size: "xs", color: "secondary", children: description }) }))] }));
|
|
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
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Clock } from 'lucide-react';
|
|
2
|
+
import { Circle, CircleCheckBig, Clock } from 'lucide-react';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { CalendarLike, CalendarItem } from './calendar-like';
|
|
5
|
+
import { Cell } from '../cell/cell';
|
|
6
|
+
import { Typo } from '../text/typo';
|
|
5
7
|
const meta = {
|
|
6
8
|
title: 'Information/CalendarLike',
|
|
7
9
|
component: CalendarLike,
|
|
@@ -89,6 +91,19 @@ export const Interactive = {
|
|
|
89
91
|
console.log(`Task ${taskId} clicked`);
|
|
90
92
|
alert(`Клик по задаче ${taskId}`);
|
|
91
93
|
};
|
|
92
|
-
return (_jsxs("div", { style: { height: '600px', overflowY: 'auto', background: 'var(--bg-primary)', padding: '20px' }, children: [_jsxs("p", { style: { marginBottom: '20px', color: 'var(--secondary)' }, children: ["\u0418\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044F: ", _jsx("br", {}), "1. \u041F\u0435\u0440\u0435\u0442\u0430\u0441\u043A\u0438\u0432\u0430\u0439\u0442\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u043C\u0435\u0436\u0434\u0443 \u0447\u0430\u0441\u0430\u043C\u0438. ", _jsx("br", {}), "2. \u0418\u0437\u043C\u0435\u043D\u044F\u0439\u0442\u0435 \u0434\u043B\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0438, \u043F\u043E\u0442\u044F\u043D\u0443\u0432 \u0437\u0430 \u043D\u0438\u0436\u043D\u0438\u0439 \u043A\u0440\u0430\u0439. ", _jsx("br", {}), "3. \u0421\u043E\u0437\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043B\u0438\u043A\u043D\u0443\u0432 \u0438 \u043F\u043E\u0442\u044F\u043D\u0443\u0432 \u043D\u0430 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u043E\u043C \u043C\u0435\u0441\u0442\u0435. ", _jsx("br", {}), "\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0432\u044B\u0432\u043E\u0434\u044F\u0442\u0441\u044F \u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u044C."] }), _jsxs("div", { style: { marginBottom: '20px', color: 'var(--secondary)', display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { type: "checkbox", checked: showCompleted, onChange: (e) => setShowCompleted(e.target.checked), id: "show-completed" }), _jsx("label", { htmlFor: "show-completed", style: { cursor: 'pointer' }, children: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043D\u044B\u0435" })] }), _jsx(CalendarLike, { tasks: filteredTasks, onTaskDrop: handleTaskDrop, onTaskResize: handleTaskResize, onCreateTask: handleCreateTask, renderTask: (task) =>
|
|
94
|
+
return (_jsxs("div", { style: { height: '600px', overflowY: 'auto', background: 'var(--bg-primary)', padding: '20px' }, children: [_jsxs("p", { style: { marginBottom: '20px', color: 'var(--secondary)' }, children: ["\u0418\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044F: ", _jsx("br", {}), "1. \u041F\u0435\u0440\u0435\u0442\u0430\u0441\u043A\u0438\u0432\u0430\u0439\u0442\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u043C\u0435\u0436\u0434\u0443 \u0447\u0430\u0441\u0430\u043C\u0438. ", _jsx("br", {}), "2. \u0418\u0437\u043C\u0435\u043D\u044F\u0439\u0442\u0435 \u0434\u043B\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0438, \u043F\u043E\u0442\u044F\u043D\u0443\u0432 \u0437\u0430 \u043D\u0438\u0436\u043D\u0438\u0439 \u043A\u0440\u0430\u0439. ", _jsx("br", {}), "3. \u0421\u043E\u0437\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043B\u0438\u043A\u043D\u0443\u0432 \u0438 \u043F\u043E\u0442\u044F\u043D\u0443\u0432 \u043D\u0430 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u043E\u043C \u043C\u0435\u0441\u0442\u0435. ", _jsx("br", {}), "\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0432\u044B\u0432\u043E\u0434\u044F\u0442\u0441\u044F \u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u044C."] }), _jsxs("div", { style: { marginBottom: '20px', color: 'var(--secondary)', display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { type: "checkbox", checked: showCompleted, onChange: (e) => setShowCompleted(e.target.checked), id: "show-completed" }), _jsx("label", { htmlFor: "show-completed", style: { cursor: 'pointer' }, children: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043D\u044B\u0435" })] }), _jsx(CalendarLike, { tasks: filteredTasks, onTaskDrop: handleTaskDrop, onTaskResize: handleTaskResize, onCreateTask: handleCreateTask, renderTask: (task) => {
|
|
95
|
+
const isCompleted = task.isCompleted;
|
|
96
|
+
return (_jsx(CalendarItem, { title: _jsx(Cell, { subtitle: _jsx(Typo, { size: 'xs', weight: '500', children: "CODE-1" }), title: _jsx(Typo, { weight: "500", textDecoration: isCompleted ? 'line-through' : 'none', className: "truncate", children: task.title }), accLeft: [
|
|
97
|
+
{
|
|
98
|
+
icon: isCompleted ? CircleCheckBig : Circle,
|
|
99
|
+
color: 'var(--accent)',
|
|
100
|
+
strokeWidth: 3,
|
|
101
|
+
onClick: (e) => {
|
|
102
|
+
e?.stopPropagation();
|
|
103
|
+
setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, isCompleted: !t.isCompleted } : t)));
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
] }), color: task.color, isCompleted: isCompleted, isInWork: task.isInWork, icon: task.isInWork ? _jsx(Clock, { size: 12, className: "text-white animate-pulse" }) : undefined, onClick: () => handleTaskClick(task.id) }));
|
|
107
|
+
} })] }));
|
|
93
108
|
},
|
|
94
109
|
};
|
|
@@ -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;
|
|
@@ -3,7 +3,7 @@ import { type FC, type MouseEvent, type ReactElement } from 'react';
|
|
|
3
3
|
import type { TTypoProps, TUIColor } from '../text/typo';
|
|
4
4
|
export type TAccessory = {
|
|
5
5
|
icon: FC<LucideProps>;
|
|
6
|
-
onClick?:
|
|
6
|
+
onClick?: (e?: MouseEvent<HTMLDivElement>) => void;
|
|
7
7
|
color?: string;
|
|
8
8
|
strokeWidth?: number;
|
|
9
9
|
bgc?: CSSStyleDeclaration['backgroundColor'];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { LucideProps } from 'lucide-react';
|
|
2
|
-
import type { FC, ReactElement } from 'react';
|
|
2
|
+
import type { FC, MouseEvent, ReactElement } from 'react';
|
|
3
3
|
type TIconActionProps = {
|
|
4
4
|
icon: ReactElement<LucideProps>;
|
|
5
|
-
onClick?:
|
|
5
|
+
onClick?: (e?: MouseEvent<HTMLDivElement>) => void;
|
|
6
6
|
bgc?: CSSStyleDeclaration['backgroundColor'];
|
|
7
7
|
};
|
|
8
8
|
export declare const IconAction: FC<TIconActionProps>;
|
|
@@ -4,7 +4,7 @@ export const IconAction = ({ onClick, icon, bgc }) => {
|
|
|
4
4
|
const onClickHandler = (e) => {
|
|
5
5
|
e.stopPropagation();
|
|
6
6
|
if (onClick) {
|
|
7
|
-
onClick();
|
|
7
|
+
onClick(e);
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
return (_jsx("div", { className: jc('relative z-10 flex h-8 w-8 items-center justify-center rounded transition-all duration-200 ease-in-out', onClick ? 'hover:bg-[var(--shadow)] cursor-pointer' : undefined), onClick: onClickHandler, children: _jsx("span", { className: "absolute left-1/2 top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full", style: { backgroundColor: bgc }, children: icon }) }));
|