@monkey-mini-app/ui 0.1.0 → 0.1.4

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.
Files changed (203) hide show
  1. package/README.md +58 -0
  2. package/dist/globals.css +1 -1
  3. package/dist/index.js +12 -2
  4. package/dist/manifest.json +6 -6
  5. package/dist/runtime.js +9 -0
  6. package/dist/sdk.js +158 -0
  7. package/dist/src/blocks/activity-feed.tsx +7 -0
  8. package/dist/src/blocks/app-shell.tsx +1 -0
  9. package/dist/src/blocks/attachment-gallery.tsx +7 -0
  10. package/dist/src/blocks/bar-chart.tsx +15 -1
  11. package/dist/src/blocks/comment-thread.tsx +7 -0
  12. package/dist/src/blocks/dashboard-shell.tsx +109 -0
  13. package/dist/src/blocks/description-list.tsx +7 -0
  14. package/dist/src/blocks/detail-panel.tsx +1 -0
  15. package/dist/src/blocks/donut-chart.tsx +19 -2
  16. package/dist/src/blocks/env-badge.tsx +7 -0
  17. package/dist/src/blocks/env-table.tsx +316 -0
  18. package/dist/src/blocks/file-tree.tsx +7 -0
  19. package/dist/src/blocks/filter-bar.tsx +1 -0
  20. package/dist/src/blocks/form-sheet.tsx +160 -0
  21. package/dist/src/blocks/gauge.tsx +29 -6
  22. package/dist/src/blocks/list-detail.tsx +121 -0
  23. package/dist/src/blocks/notification-center.tsx +7 -0
  24. package/dist/src/blocks/page-header.tsx +1 -0
  25. package/dist/src/blocks/progress-ring.tsx +20 -2
  26. package/dist/src/blocks/radar-chart.tsx +18 -1
  27. package/dist/src/blocks/request-inspector.tsx +7 -0
  28. package/dist/src/blocks/reveal.tsx +56 -0
  29. package/dist/src/blocks/run-timeline.tsx +7 -0
  30. package/dist/src/blocks/scrollspy.tsx +7 -0
  31. package/dist/src/blocks/settings-split.tsx +154 -0
  32. package/dist/src/blocks/severity-chip.tsx +7 -0
  33. package/dist/src/blocks/sparkline.tsx +12 -3
  34. package/dist/src/blocks/stat-card.tsx +1 -0
  35. package/dist/src/blocks/status-badge.tsx +1 -0
  36. package/dist/src/blocks/table-page.tsx +83 -0
  37. package/dist/src/blocks/terminal.tsx +21 -3
  38. package/dist/src/blocks/test-step-list.tsx +7 -0
  39. package/dist/src/blocks/trend-card.tsx +7 -0
  40. package/dist/src/blocks/wizard-shell.tsx +132 -0
  41. package/dist/src/cdn-modules.d.ts +5 -0
  42. package/dist/src/components/accordion.tsx +7 -0
  43. package/dist/src/components/alert-dialog.tsx +7 -0
  44. package/dist/src/components/alert.tsx +7 -0
  45. package/dist/src/components/aspect-ratio.tsx +4 -0
  46. package/dist/src/components/attachment.tsx +4 -0
  47. package/dist/src/components/avatar.tsx +4 -0
  48. package/dist/src/components/badge.tsx +7 -0
  49. package/dist/src/components/breadcrumb.tsx +4 -0
  50. package/dist/src/components/bubble.tsx +4 -0
  51. package/dist/src/components/button-group.tsx +6 -0
  52. package/dist/src/components/button.tsx +7 -0
  53. package/dist/src/components/calendar.tsx +4 -0
  54. package/dist/src/components/card.tsx +8 -1
  55. package/dist/src/components/chart.tsx +7 -0
  56. package/dist/src/components/checkbox.tsx +5 -0
  57. package/dist/src/components/collapsible.tsx +4 -0
  58. package/dist/src/components/combobox.tsx +4 -0
  59. package/dist/src/components/command.tsx +6 -0
  60. package/dist/src/components/context-menu.tsx +4 -0
  61. package/dist/src/components/dialog.tsx +21 -0
  62. package/dist/src/components/drawer.tsx +4 -0
  63. package/dist/src/components/dropdown-menu.tsx +7 -0
  64. package/dist/src/components/empty.tsx +7 -0
  65. package/dist/src/components/field.tsx +6 -0
  66. package/dist/src/components/hover-card.tsx +4 -0
  67. package/dist/src/components/input-group.tsx +4 -0
  68. package/dist/src/components/input.tsx +7 -0
  69. package/dist/src/components/item.tsx +4 -0
  70. package/dist/src/components/kbd.tsx +4 -0
  71. package/dist/src/components/label.tsx +7 -0
  72. package/dist/src/components/marker.tsx +4 -0
  73. package/dist/src/components/menubar.tsx +4 -0
  74. package/dist/src/components/message-scroller.tsx +4 -0
  75. package/dist/src/components/message.tsx +4 -0
  76. package/dist/src/components/native-select.tsx +4 -0
  77. package/dist/src/components/navigation-menu.tsx +4 -0
  78. package/dist/src/components/pagination.tsx +4 -0
  79. package/dist/src/components/popover.tsx +7 -0
  80. package/dist/src/components/progress.tsx +7 -0
  81. package/dist/src/components/questionnaire.tsx +4 -0
  82. package/dist/src/components/radio-group.tsx +4 -0
  83. package/dist/src/components/resizable.tsx +7 -0
  84. package/dist/src/components/scroll-area.tsx +4 -0
  85. package/dist/src/components/select.tsx +7 -0
  86. package/dist/src/components/separator.tsx +5 -0
  87. package/dist/src/components/sheet.tsx +22 -0
  88. package/dist/src/components/sidebar.tsx +4 -0
  89. package/dist/src/components/skeleton.tsx +7 -0
  90. package/dist/src/components/slider.tsx +4 -0
  91. package/dist/src/components/spinner.tsx +6 -0
  92. package/dist/src/components/switch.tsx +7 -0
  93. package/dist/src/components/table.tsx +7 -0
  94. package/dist/src/components/tabs.tsx +7 -0
  95. package/dist/src/components/textarea.tsx +7 -0
  96. package/dist/src/components/toast.tsx +7 -0
  97. package/dist/src/components/toggle-group.tsx +4 -0
  98. package/dist/src/components/toggle.tsx +4 -0
  99. package/dist/src/components/tooltip.tsx +7 -0
  100. package/dist/src/composites/autocomplete.tsx +7 -0
  101. package/dist/src/composites/cascader.tsx +7 -0
  102. package/dist/src/composites/color-picker.tsx +7 -0
  103. package/dist/src/composites/confirm-dialog.tsx +7 -0
  104. package/dist/src/composites/copyable.tsx +9 -1
  105. package/dist/src/composites/currency-input.tsx +7 -0
  106. package/dist/src/composites/date-picker.tsx +7 -0
  107. package/dist/src/composites/date-range-picker.tsx +4 -0
  108. package/dist/src/composites/date-time-picker.tsx +7 -0
  109. package/dist/src/composites/date-time-range-picker.tsx +7 -0
  110. package/dist/src/composites/duration-input.tsx +7 -0
  111. package/dist/src/composites/mini-calendar.tsx +7 -0
  112. package/dist/src/composites/number-field.tsx +7 -0
  113. package/dist/src/composites/password-field.tsx +7 -0
  114. package/dist/src/composites/phone-input.tsx +7 -0
  115. package/dist/src/composites/rating.tsx +7 -0
  116. package/dist/src/composites/relative-date-picker.tsx +7 -0
  117. package/dist/src/composites/search-input.tsx +7 -0
  118. package/dist/src/composites/slider-range.tsx +7 -0
  119. package/dist/src/composites/tag-input.tsx +7 -0
  120. package/dist/src/composites/time-picker.tsx +11 -1
  121. package/dist/src/composites/time-range-picker.tsx +7 -0
  122. package/dist/src/composites/timezone-select.tsx +7 -0
  123. package/dist/src/composites/transfer.tsx +7 -0
  124. package/dist/src/composites/user-picker.tsx +7 -0
  125. package/dist/src/hooks/use-html-dark.ts +37 -0
  126. package/dist/src/hooks/use-mobile.ts +3 -0
  127. package/dist/src/i18n/en.ts +109 -2
  128. package/dist/src/i18n/zh.ts +99 -2
  129. package/dist/src/index.ts +147 -134
  130. package/dist/src/lib/clipboard.ts +10 -0
  131. package/dist/src/lib/illustrations.tsx +2 -2
  132. package/dist/src/products/code-block.tsx +35 -18
  133. package/dist/src/products/code-editor.tsx +156 -24
  134. package/dist/src/products/commit-graph.tsx +644 -0
  135. package/dist/src/products/data-grid.tsx +2 -1
  136. package/dist/src/products/diff-viewer.tsx +104 -8
  137. package/dist/src/products/event-calendar.tsx +28 -823
  138. package/dist/src/products/file-dropzone.tsx +7 -0
  139. package/dist/src/products/full-calendar/animations.ts +38 -0
  140. package/dist/src/products/full-calendar/calendar-body.tsx +77 -0
  141. package/dist/src/products/full-calendar/constants.ts +10 -0
  142. package/dist/src/products/full-calendar/contexts/calendar-context.tsx +255 -0
  143. package/dist/src/products/full-calendar/contexts/dnd-context.tsx +157 -0
  144. package/dist/src/products/full-calendar/dialogs/add-edit-event-dialog.tsx +187 -0
  145. package/dist/src/products/full-calendar/dialogs/delete-event-dialog.tsx +39 -0
  146. package/dist/src/products/full-calendar/dialogs/event-details-dialog.tsx +100 -0
  147. package/dist/src/products/full-calendar/dialogs/events-list-dialog.tsx +87 -0
  148. package/dist/src/products/full-calendar/dnd/draggable-event.tsx +45 -0
  149. package/dist/src/products/full-calendar/dnd/droppable-area.tsx +45 -0
  150. package/dist/src/products/full-calendar/dnd/resizable-event.tsx +179 -0
  151. package/dist/src/products/full-calendar/header/calendar-header.tsx +65 -0
  152. package/dist/src/products/full-calendar/header/date-navigator.tsx +110 -0
  153. package/dist/src/products/full-calendar/header/filter.tsx +58 -0
  154. package/dist/src/products/full-calendar/header/today-button.tsx +45 -0
  155. package/dist/src/products/full-calendar/header/user-select.tsx +64 -0
  156. package/dist/src/products/full-calendar/header/view-tabs.tsx +36 -0
  157. package/dist/src/products/full-calendar/helpers.ts +453 -0
  158. package/dist/src/products/full-calendar/hooks/use-time-range-create.ts +152 -0
  159. package/dist/src/products/full-calendar/hooks.ts +70 -0
  160. package/dist/src/products/full-calendar/interfaces.ts +23 -0
  161. package/dist/src/products/full-calendar/settings/settings.tsx +80 -0
  162. package/dist/src/products/full-calendar/shims/avatar-group.tsx +16 -0
  163. package/dist/src/products/full-calendar/shims/day-picker.tsx +1 -0
  164. package/dist/src/products/full-calendar/shims/motion.tsx +97 -0
  165. package/dist/src/products/full-calendar/shims/next-themes.ts +6 -0
  166. package/dist/src/products/full-calendar/shims/re-resizable.tsx +25 -0
  167. package/dist/src/products/full-calendar/shims/sonner.ts +4 -0
  168. package/dist/src/products/full-calendar/types.ts +8 -0
  169. package/dist/src/products/full-calendar/views/agenda-view/agenda-events.tsx +140 -0
  170. package/dist/src/products/full-calendar/views/month-view/calendar-month-view.tsx +69 -0
  171. package/dist/src/products/full-calendar/views/month-view/day-cell.tsx +201 -0
  172. package/dist/src/products/full-calendar/views/month-view/event-bullet.tsx +40 -0
  173. package/dist/src/products/full-calendar/views/month-view/month-event-badge.tsx +143 -0
  174. package/dist/src/products/full-calendar/views/week-and-day-view/calendar-day-view.tsx +295 -0
  175. package/dist/src/products/full-calendar/views/week-and-day-view/calendar-time-line.tsx +35 -0
  176. package/dist/src/products/full-calendar/views/week-and-day-view/calendar-week-view.tsx +253 -0
  177. package/dist/src/products/full-calendar/views/week-and-day-view/day-view-multi-day-events-row.tsx +73 -0
  178. package/dist/src/products/full-calendar/views/week-and-day-view/event-block.tsx +114 -0
  179. package/dist/src/products/full-calendar/views/week-and-day-view/render-grouped-events.tsx +49 -0
  180. package/dist/src/products/full-calendar/views/week-and-day-view/week-view-multi-day-events-row.tsx +143 -0
  181. package/dist/src/products/full-calendar/views/year-view/calendar-year-view.tsx +161 -0
  182. package/dist/src/products/gantt.tsx +7 -0
  183. package/dist/src/products/jira-wiki.tsx +1 -0
  184. package/dist/src/products/jql-input.tsx +340 -37
  185. package/dist/src/products/json-viewer.tsx +7 -0
  186. package/dist/src/products/kanban-issue-panel.tsx +7 -0
  187. package/dist/src/products/kanban.tsx +1 -0
  188. package/dist/src/products/log-viewer.tsx +390 -18
  189. package/dist/src/products/markdown-editor.tsx +21 -6
  190. package/dist/src/products/markdown.tsx +7 -0
  191. package/dist/src/products/rich-text-editor.tsx +65 -59
  192. package/dist/src/products/sortable-list.tsx +7 -0
  193. package/dist/src/products/stepper.tsx +219 -70
  194. package/dist/src/products/timeline.tsx +7 -0
  195. package/dist/src/products/tree-view.tsx +7 -0
  196. package/dist/src/sdk/app-error-boundary.tsx +240 -0
  197. package/dist/src/sdk/app-id.ts +7 -0
  198. package/dist/src/sdk/use-app.ts +126 -0
  199. package/dist/src/styles/globals.css +7 -7
  200. package/dist/vendors/lodash.js +20 -0
  201. package/dist/vendors/motion.js +12 -0
  202. package/package.json +23 -19
  203. package/dist/src/products/jql-language.ts +0 -70
@@ -6,6 +6,13 @@ import { cn } from "@monkey-mini-app/ui/lib/utils"
6
6
  import { useLabels } from "@monkey-mini-app/ui/i18n/context"
7
7
  import { Attachment, AttachmentContent, AttachmentTitle } from "@monkey-mini-app/ui/components/attachment"
8
8
 
9
+ /**
10
+ * Drag-drop + click file picker.
11
+ * @when Getting a local File into the app (then upload via `ctx.http` in the api layer).
12
+ * @example
13
+ * <FileDropzone onFiles={(fs) => set(fs)} />
14
+ * @family Form
15
+ */
9
16
  export function FileDropzone({
10
17
  files,
11
18
  onFiles,
@@ -0,0 +1,38 @@
1
+ import type { Variants } from "./shims/motion";
2
+
3
+ export const fadeIn: Variants = {
4
+ initial: { opacity: 0 },
5
+ animate: { opacity: 1 },
6
+ exit: { opacity: 0 },
7
+ };
8
+
9
+ export const slideFromLeft: Variants = {
10
+ initial: { x: -20, opacity: 0 },
11
+ animate: { x: 0, opacity: 1 },
12
+ exit: { x: 20, opacity: 0 },
13
+ };
14
+
15
+ export const slideFromRight: Variants = {
16
+ initial: { x: 20, opacity: 0 },
17
+ animate: { x: 0, opacity: 1 },
18
+ exit: { x: -20, opacity: 0 },
19
+ };
20
+
21
+ export const transition = {
22
+ type: "spring",
23
+ stiffness: 200,
24
+ damping: 20,
25
+ } as const;
26
+
27
+ export const staggerContainer: Variants = {
28
+ animate: {
29
+ transition: {
30
+ staggerChildren: 0.1,
31
+ },
32
+ },
33
+ };
34
+
35
+ export const buttonHover: Variants = {
36
+ hover: { scale: 1.05 },
37
+ tap: { scale: 0.95 },
38
+ };
@@ -0,0 +1,77 @@
1
+ "use client";
2
+
3
+ import { isSameDay, parseISO } from "date-fns";
4
+ import { motion } from "./shims/motion";
5
+ import { fadeIn, transition } from "./animations";
6
+ import { useCalendar } from "./contexts/calendar-context";
7
+ import { AgendaEvents } from "./views/agenda-view/agenda-events";
8
+ import { CalendarMonthView } from "./views/month-view/calendar-month-view";
9
+ import { CalendarDayView } from "./views/week-and-day-view/calendar-day-view";
10
+ import { CalendarWeekView } from "./views/week-and-day-view/calendar-week-view";
11
+ import { CalendarYearView } from "./views/year-view/calendar-year-view";
12
+
13
+ export function CalendarBody() {
14
+ const { view, events } = useCalendar();
15
+
16
+ const singleDayEvents = events.filter((event) => {
17
+ const startDate = parseISO(event.startDate);
18
+ const endDate = parseISO(event.endDate);
19
+ return isSameDay(startDate, endDate);
20
+ });
21
+
22
+ const multiDayEvents = events.filter((event) => {
23
+ const startDate = parseISO(event.startDate);
24
+ const endDate = parseISO(event.endDate);
25
+ return !isSameDay(startDate, endDate);
26
+ });
27
+
28
+ return (
29
+ <div className="w-full h-full overflow-scroll relative">
30
+ <motion.div
31
+ key={view}
32
+ initial="initial"
33
+ animate="animate"
34
+ exit="exit"
35
+ variants={fadeIn}
36
+ transition={transition}
37
+ >
38
+ {view === "month" && (
39
+ <CalendarMonthView
40
+ singleDayEvents={singleDayEvents}
41
+ multiDayEvents={multiDayEvents}
42
+ />
43
+ )}
44
+ {view === "week" && (
45
+ <CalendarWeekView
46
+ singleDayEvents={singleDayEvents}
47
+ multiDayEvents={multiDayEvents}
48
+ />
49
+ )}
50
+ {view === "day" && (
51
+ <CalendarDayView
52
+ singleDayEvents={singleDayEvents}
53
+ multiDayEvents={multiDayEvents}
54
+ />
55
+ )}
56
+ {view === "year" && (
57
+ <CalendarYearView
58
+ singleDayEvents={singleDayEvents}
59
+ multiDayEvents={multiDayEvents}
60
+ />
61
+ )}
62
+ {view === "agenda" && (
63
+ <motion.div
64
+ key="agenda"
65
+ initial="initial"
66
+ animate="animate"
67
+ exit="exit"
68
+ variants={fadeIn}
69
+ transition={transition}
70
+ >
71
+ <AgendaEvents />
72
+ </motion.div>
73
+ )}
74
+ </motion.div>
75
+ </div>
76
+ );
77
+ }
@@ -0,0 +1,10 @@
1
+ import type { TEventColor } from "./types";
2
+
3
+ export const COLORS: TEventColor[] = [
4
+ "blue",
5
+ "green",
6
+ "red",
7
+ "yellow",
8
+ "purple",
9
+ "orange",
10
+ ];
@@ -0,0 +1,255 @@
1
+ "use client";
2
+
3
+ import type React from "react";
4
+ import { createContext, useContext, useEffect, useState } from "react";
5
+ import { useLocalStorage } from "../hooks";
6
+ import type { IEvent, IUser } from "../interfaces";
7
+ import type { TCalendarView, TEventColor } from "../types";
8
+
9
+ interface ICalendarContext {
10
+ selectedDate: Date;
11
+ view: TCalendarView;
12
+ setView: (view: TCalendarView) => void;
13
+ agendaModeGroupBy: "date" | "color";
14
+ setAgendaModeGroupBy: (groupBy: "date" | "color") => void;
15
+ use24HourFormat: boolean;
16
+ toggleTimeFormat: () => void;
17
+ startOfDayHour: number;
18
+ setStartOfDayHour: (newVal: number) => void;
19
+ setSelectedDate: (date: Date | undefined) => void;
20
+ selectedUserId: IUser["id"] | "all";
21
+ setSelectedUserId: (userId: IUser["id"] | "all") => void;
22
+ badgeVariant: "dot" | "colored";
23
+ setBadgeVariant: (variant: "dot" | "colored") => void;
24
+ selectedColors: TEventColor[];
25
+ filterEventsBySelectedColors: (colors: TEventColor) => void;
26
+ filterEventsBySelectedUser: (userId: IUser["id"] | "all") => void;
27
+ users: IUser[];
28
+ events: IEvent[];
29
+ addEvent: (event: IEvent) => void;
30
+ updateEvent: (event: IEvent) => void;
31
+ removeEvent: (eventId: number) => void;
32
+ clearFilter: () => void;
33
+ }
34
+
35
+ interface CalendarSettings {
36
+ badgeVariant: "dot" | "colored";
37
+ view: TCalendarView;
38
+ use24HourFormat: boolean;
39
+ startOfDayHour: number;
40
+ agendaModeGroupBy: "date" | "color";
41
+ }
42
+
43
+ export const MIN_SCROLL_HOUR = 0;
44
+ // With a fixed calendar height, having 16h on top
45
+ // of the frame allows to see the rest of the day
46
+ export const MAX_SCROLL_HOUR = 16;
47
+
48
+ const DEFAULT_SETTINGS: CalendarSettings = {
49
+ badgeVariant: "colored",
50
+ view: "day",
51
+ use24HourFormat: true,
52
+ startOfDayHour: 8,
53
+ agendaModeGroupBy: "date",
54
+ };
55
+
56
+ const CalendarContext = createContext({} as ICalendarContext);
57
+
58
+ export function CalendarProvider({
59
+ children,
60
+ users,
61
+ events,
62
+ badge = "colored",
63
+ view = "day",
64
+ date,
65
+ onEventsChange,
66
+ }: {
67
+ children: React.ReactNode;
68
+ users: IUser[];
69
+ events: IEvent[];
70
+ view?: TCalendarView;
71
+ badge?: "dot" | "colored";
72
+ date?: Date;
73
+ onEventsChange?: (events: IEvent[]) => void;
74
+ }) {
75
+
76
+ const [rawSettings, setSettings] = useLocalStorage<Partial<CalendarSettings>>("calendar-settings", {});
77
+
78
+ const settings: CalendarSettings = {
79
+ ...DEFAULT_SETTINGS,
80
+ badgeVariant: badge,
81
+ view: view,
82
+ ...rawSettings,
83
+ };
84
+
85
+ const [badgeVariant, setBadgeVariantState] = useState<"dot" | "colored">(
86
+ settings.badgeVariant,
87
+ );
88
+ const [currentView, setCurrentViewState] = useState<TCalendarView>(
89
+ settings.view,
90
+ );
91
+ const [use24HourFormat, setUse24HourFormatState] = useState<boolean>(
92
+ settings.use24HourFormat,
93
+ );
94
+ const [startOfDayHour, setStartOfDayHourState] = useState<number>(
95
+ settings.startOfDayHour,
96
+ );
97
+ const [agendaModeGroupBy, setAgendaModeGroupByState] = useState<
98
+ "date" | "color"
99
+ >(settings.agendaModeGroupBy);
100
+
101
+ const [selectedDate, setSelectedDate] = useState(date ?? new Date());
102
+ const [selectedUserId, setSelectedUserId] = useState<IUser["id"] | "all">(
103
+ "all",
104
+ );
105
+ const [selectedColors, setSelectedColors] = useState<TEventColor[]>([]);
106
+
107
+ const [allEvents, setAllEvents] = useState<IEvent[]>(events || []);
108
+ const [filteredEvents, setFilteredEvents] = useState<IEvent[]>(events || []);
109
+
110
+ useEffect(() => {
111
+ setAllEvents(events || []);
112
+ setFilteredEvents(events || []);
113
+ }, [events]);
114
+
115
+ const updateSettings = (newPartialSettings: Partial<CalendarSettings>) => {
116
+ setSettings((prev) => ({
117
+ ...prev,
118
+ ...newPartialSettings,
119
+ }));
120
+ };
121
+
122
+ const setBadgeVariant = (variant: "dot" | "colored") => {
123
+ setBadgeVariantState(variant);
124
+ updateSettings({ badgeVariant: variant });
125
+ };
126
+
127
+ const setView = (newView: TCalendarView) => {
128
+ setCurrentViewState(newView);
129
+ updateSettings({ view: newView });
130
+ };
131
+
132
+ const toggleTimeFormat = () => {
133
+ const newValue = !use24HourFormat;
134
+ setUse24HourFormatState(newValue);
135
+ updateSettings({ use24HourFormat: newValue });
136
+ };
137
+
138
+ const setStartOfDayHour = (newVal: number) => {
139
+ if (!isNaN(newVal) && newVal >= MIN_SCROLL_HOUR && newVal <= MAX_SCROLL_HOUR) {
140
+ setStartOfDayHourState(newVal);
141
+ updateSettings({ startOfDayHour: newVal });
142
+ }
143
+ };
144
+
145
+
146
+ const setAgendaModeGroupBy = (groupBy: "date" | "color") => {
147
+ setAgendaModeGroupByState(groupBy);
148
+ updateSettings({ agendaModeGroupBy: groupBy });
149
+ };
150
+
151
+ const filterEventsBySelectedColors = (color: TEventColor) => {
152
+ const isColorSelected = selectedColors.includes(color);
153
+ const newColors = isColorSelected
154
+ ? selectedColors.filter((c) => c !== color)
155
+ : [...selectedColors, color];
156
+
157
+ if (newColors.length > 0) {
158
+ const filtered = allEvents.filter((event) => {
159
+ const eventColor = event.color || "blue";
160
+ return newColors.includes(eventColor);
161
+ });
162
+ setFilteredEvents(filtered);
163
+ } else {
164
+ setFilteredEvents(allEvents);
165
+ }
166
+
167
+ setSelectedColors(newColors);
168
+ };
169
+
170
+ const filterEventsBySelectedUser = (userId: IUser["id"] | "all") => {
171
+ setSelectedUserId(userId);
172
+ if (userId === "all") {
173
+ setFilteredEvents(allEvents);
174
+ } else {
175
+ const filtered = allEvents.filter((event) => event.user.id === userId);
176
+ setFilteredEvents(filtered);
177
+ }
178
+ };
179
+
180
+ const handleSelectDate = (date: Date | undefined) => {
181
+ if (!date) return;
182
+ setSelectedDate(date);
183
+ };
184
+
185
+ const addEvent = (event: IEvent) => {
186
+ const next = [...allEvents, event];
187
+ setAllEvents(next);
188
+ setFilteredEvents((prev) => [...prev, event]);
189
+ onEventsChange?.(next);
190
+ };
191
+
192
+ const updateEvent = (event: IEvent) => {
193
+ const updated = {
194
+ ...event,
195
+ startDate: new Date(event.startDate).toISOString(),
196
+ endDate: new Date(event.endDate).toISOString(),
197
+ };
198
+ const next = allEvents.map((e) => (e.id === event.id ? updated : e));
199
+ setAllEvents(next);
200
+ setFilteredEvents((prev) => prev.map((e) => (e.id === event.id ? updated : e)));
201
+ onEventsChange?.(next);
202
+ };
203
+
204
+ const removeEvent = (eventId: number) => {
205
+ const next = allEvents.filter((e) => e.id !== eventId);
206
+ setAllEvents(next);
207
+ setFilteredEvents((prev) => prev.filter((e) => e.id !== eventId));
208
+ onEventsChange?.(next);
209
+ };
210
+
211
+ const clearFilter = () => {
212
+ setFilteredEvents(allEvents);
213
+ setSelectedColors([]);
214
+ setSelectedUserId("all");
215
+ };
216
+
217
+ const value = {
218
+ selectedDate,
219
+ setSelectedDate: handleSelectDate,
220
+ selectedUserId,
221
+ setSelectedUserId,
222
+ badgeVariant,
223
+ setBadgeVariant,
224
+ users,
225
+ selectedColors,
226
+ filterEventsBySelectedColors,
227
+ filterEventsBySelectedUser,
228
+ events: filteredEvents,
229
+ view: currentView,
230
+ use24HourFormat,
231
+ toggleTimeFormat,
232
+ startOfDayHour,
233
+ setStartOfDayHour,
234
+ setView,
235
+ agendaModeGroupBy,
236
+ setAgendaModeGroupBy,
237
+ addEvent,
238
+ updateEvent,
239
+ removeEvent,
240
+ clearFilter,
241
+ };
242
+
243
+ return (
244
+ <CalendarContext.Provider value={value}>
245
+ {children}
246
+ </CalendarContext.Provider>
247
+ );
248
+ }
249
+
250
+ export function useCalendar(): ICalendarContext {
251
+ const context = useContext(CalendarContext);
252
+ if (!context)
253
+ throw new Error("useCalendar must be used within a CalendarProvider.");
254
+ return context;
255
+ }
@@ -0,0 +1,157 @@
1
+ "use client";
2
+
3
+ import React, {
4
+ createContext,
5
+ type ReactNode,
6
+ useCallback,
7
+ useContext,
8
+ useRef,
9
+ useState,
10
+ useMemo,
11
+ } from "react";
12
+ import { toast } from "../shims/sonner";
13
+ import { useCalendar } from "./calendar-context";
14
+ import type { IEvent } from "../interfaces";
15
+
16
+ interface DragDropContextType {
17
+ draggedEvent: IEvent | null;
18
+ isDragging: boolean;
19
+ startDrag: (event: IEvent) => void;
20
+ endDrag: () => void;
21
+ handleEventDrop: (date: Date, hour?: number, minute?: number) => void;
22
+ }
23
+
24
+ interface DndProviderProps {
25
+ children: ReactNode;
26
+ }
27
+
28
+ const DragDropContext = createContext<DragDropContextType | undefined>(
29
+ undefined,
30
+ );
31
+
32
+ export function DndProvider({ children }: DndProviderProps) {
33
+ const { updateEvent } = useCalendar();
34
+ const [dragState, setDragState] = useState<{
35
+ draggedEvent: IEvent | null;
36
+ isDragging: boolean;
37
+ }>({ draggedEvent: null, isDragging: false });
38
+
39
+ const onEventDroppedRef = useRef<
40
+ ((event: IEvent, newStartDate: Date, newEndDate: Date) => void) | null
41
+ >(null);
42
+
43
+ const startDrag = useCallback((event: IEvent) => {
44
+ setDragState({ draggedEvent: event, isDragging: true });
45
+ }, []);
46
+
47
+ const endDrag = useCallback(() => {
48
+ setDragState({ draggedEvent: null, isDragging: false });
49
+ }, []);
50
+
51
+ const calculateNewDates = useCallback(
52
+ (event: IEvent, targetDate: Date, hour?: number, minute?: number) => {
53
+ const originalStart = new Date(event.startDate);
54
+ const originalEnd = new Date(event.endDate);
55
+ const duration = originalEnd.getTime() - originalStart.getTime();
56
+
57
+ const newStart = new Date(targetDate);
58
+ if (hour !== undefined) {
59
+ newStart.setHours(hour, minute || 0, 0, 0);
60
+ } else {
61
+ newStart.setHours(
62
+ originalStart.getHours(),
63
+ originalStart.getMinutes(),
64
+ 0,
65
+ 0,
66
+ );
67
+ }
68
+
69
+ return {
70
+ newStart,
71
+ newEnd: new Date(newStart.getTime() + duration),
72
+ };
73
+ },
74
+ [],
75
+ );
76
+
77
+ const isSamePosition = useCallback((date1: Date, date2: Date) => {
78
+ return date1.getTime() === date2.getTime();
79
+ }, []);
80
+
81
+ const handleEventDrop = useCallback(
82
+ (targetDate: Date, hour?: number, minute?: number) => {
83
+ const { draggedEvent } = dragState;
84
+ if (!draggedEvent) return;
85
+
86
+ const { newStart, newEnd } = calculateNewDates(
87
+ draggedEvent,
88
+ targetDate,
89
+ hour,
90
+ minute,
91
+ );
92
+ const originalStart = new Date(draggedEvent.startDate);
93
+
94
+ // Check if dropped in same position
95
+ if (isSamePosition(originalStart, newStart)) {
96
+ endDrag();
97
+ return;
98
+ }
99
+
100
+ // Instantly update event
101
+ const callback = onEventDroppedRef.current;
102
+ if (callback) {
103
+ callback(draggedEvent, newStart, newEnd);
104
+ }
105
+ endDrag();
106
+ },
107
+ [dragState, calculateNewDates, isSamePosition, endDrag],
108
+ );
109
+
110
+ // Default event update handler
111
+ const handleEventUpdate = useCallback(
112
+ (event: IEvent, newStartDate: Date, newEndDate: Date) => {
113
+ try {
114
+ const updatedEvent = {
115
+ ...event,
116
+ startDate: newStartDate.toISOString(),
117
+ endDate: newEndDate.toISOString(),
118
+ };
119
+ updateEvent(updatedEvent);
120
+ toast.success("Event updated successfully");
121
+ } catch {
122
+ toast.error("Failed to update event");
123
+ }
124
+ },
125
+ [updateEvent],
126
+ );
127
+
128
+ // Set default callback
129
+ React.useEffect(() => {
130
+ onEventDroppedRef.current = handleEventUpdate;
131
+ }, [handleEventUpdate]);
132
+
133
+ const contextValue = useMemo(
134
+ () => ({
135
+ draggedEvent: dragState.draggedEvent,
136
+ isDragging: dragState.isDragging,
137
+ startDrag,
138
+ endDrag,
139
+ handleEventDrop,
140
+ }),
141
+ [dragState, startDrag, endDrag, handleEventDrop],
142
+ );
143
+
144
+ return (
145
+ <DragDropContext.Provider value={contextValue}>
146
+ {children}
147
+ </DragDropContext.Provider>
148
+ );
149
+ }
150
+
151
+ export function useDragDrop() {
152
+ const context = useContext(DragDropContext);
153
+ if (!context) {
154
+ throw new Error("useDragDrop must be used within a DragDropProvider");
155
+ }
156
+ return context;
157
+ }