@monkey-mini-app/ui 0.1.1 → 0.1.5

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 +12 -6
  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 +11 -0
  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 +7 -0
  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 +7 -0
  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 +7 -0
  26. package/dist/src/blocks/radar-chart.tsx +7 -0
  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 +7 -0
  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 +1 -0
  200. package/dist/vendors/lodash.js +20 -0
  201. package/dist/vendors/motion.js +12 -0
  202. package/package.json +20 -19
  203. package/dist/src/products/jql-language.ts +0 -70
@@ -0,0 +1,80 @@
1
+ import { SettingsIcon } from "lucide-react"
2
+
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuGroup,
7
+ DropdownMenuLabel,
8
+ DropdownMenuRadioGroup,
9
+ DropdownMenuRadioItem,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from "@monkey-mini-app/ui/components/dropdown-menu"
13
+ import { Input } from "@monkey-mini-app/ui/components/input"
14
+ import { Switch } from "@monkey-mini-app/ui/components/switch"
15
+ import { useLabels } from "@monkey-mini-app/ui/i18n/context"
16
+
17
+ import { MAX_SCROLL_HOUR, MIN_SCROLL_HOUR, useCalendar } from "../contexts/calendar-context"
18
+
19
+ export function Settings() {
20
+ const {
21
+ badgeVariant,
22
+ setBadgeVariant,
23
+ use24HourFormat,
24
+ toggleTimeFormat,
25
+ startOfDayHour,
26
+ setStartOfDayHour,
27
+ agendaModeGroupBy,
28
+ setAgendaModeGroupBy,
29
+ } = useCalendar()
30
+ const t = useLabels("eventCalendar")
31
+
32
+ return (
33
+ <DropdownMenu>
34
+ <DropdownMenuTrigger
35
+ className="inline-flex size-8 items-center justify-center rounded-lg border border-border bg-background"
36
+ aria-label={t.settingsAria}
37
+ >
38
+ <SettingsIcon className="h-4 w-4" />
39
+ </DropdownMenuTrigger>
40
+ <DropdownMenuContent className="w-56">
41
+ <DropdownMenuGroup>
42
+ <DropdownMenuLabel>{t.settings}</DropdownMenuLabel>
43
+ <div className="flex items-center justify-between gap-2 px-1.5 py-1.5 text-sm">
44
+ <span>{t.useDotBadge}</span>
45
+ <Switch
46
+ checked={badgeVariant === "dot"}
47
+ onCheckedChange={(checked) => setBadgeVariant(checked ? "dot" : "colored")}
48
+ />
49
+ </div>
50
+ <div className="flex items-center justify-between gap-2 px-1.5 py-1.5 text-sm">
51
+ <span>{t.use24Hour}</span>
52
+ <Switch checked={use24HourFormat} onCheckedChange={() => toggleTimeFormat()} />
53
+ </div>
54
+ <div className="flex items-center justify-between gap-2 px-1.5 py-1.5 text-sm">
55
+ <span>{t.scrollHour}</span>
56
+ <Input
57
+ type="number"
58
+ className="h-7 w-16"
59
+ min={MIN_SCROLL_HOUR}
60
+ max={MAX_SCROLL_HOUR}
61
+ value={startOfDayHour}
62
+ onChange={(e) => setStartOfDayHour(Number(e.target.value))}
63
+ onClick={(e) => e.stopPropagation()}
64
+ onKeyDown={(e) => e.stopPropagation()}
65
+ />
66
+ </div>
67
+ </DropdownMenuGroup>
68
+ <DropdownMenuSeparator />
69
+ <DropdownMenuRadioGroup
70
+ value={agendaModeGroupBy}
71
+ onValueChange={(value) => setAgendaModeGroupBy(value as "date" | "color")}
72
+ >
73
+ <DropdownMenuLabel>{t.agendaGroupBy}</DropdownMenuLabel>
74
+ <DropdownMenuRadioItem value="date">{t.groupByDate}</DropdownMenuRadioItem>
75
+ <DropdownMenuRadioItem value="color">{t.groupByColor}</DropdownMenuRadioItem>
76
+ </DropdownMenuRadioGroup>
77
+ </DropdownMenuContent>
78
+ </DropdownMenu>
79
+ )
80
+ }
@@ -0,0 +1,16 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@monkey-mini-app/ui/lib/utils"
4
+
5
+ export function AvatarGroup({
6
+ children,
7
+ className,
8
+ max = 3,
9
+ }: {
10
+ children: React.ReactNode
11
+ className?: string
12
+ max?: number
13
+ }) {
14
+ const items = React.Children.toArray(children).slice(0, max)
15
+ return <div className={cn("flex items-center -space-x-2", className)}>{items}</div>
16
+ }
@@ -0,0 +1 @@
1
+ export { Calendar as DayPicker } from "@monkey-mini-app/ui/components/calendar"
@@ -0,0 +1,97 @@
1
+ import * as React from "react"
2
+
3
+ function strip(props: Record<string, unknown>) {
4
+ const {
5
+ initial: _i,
6
+ animate: _a,
7
+ exit: _e,
8
+ variants: _v,
9
+ transition: _t,
10
+ whileHover: _h,
11
+ whileTap: _p,
12
+ layout: _l,
13
+ scrollPosition: _s,
14
+ ...rest
15
+ } = props
16
+ return rest
17
+ }
18
+
19
+ function make(tag: React.ElementType) {
20
+ const El = React.forwardRef(function MotionEl(props: Record<string, unknown>, ref: React.Ref<unknown>) {
21
+ return React.createElement(tag, { ...strip(props), ref })
22
+ })
23
+ El.displayName = `motion.${typeof tag === "string" ? tag : "el"}`
24
+ return El
25
+ }
26
+
27
+ /**
28
+ * One component per tag, cached.
29
+ *
30
+ * Without the cache `motion.div` returns a NEW component type on every property
31
+ * access, so React sees a different `type` each render and tears the subtree down:
32
+ * DOM refs held across renders (pointer capture, `getBoundingClientRect` during a
33
+ * drag) then point at detached nodes, and every `motion.*` descendant remounts on
34
+ * each state change. Keep this memoised.
35
+ */
36
+ const byTag = new Map<string, React.ElementType>()
37
+ const byComponent = new Map<React.ElementType, React.ElementType>()
38
+
39
+ function forTag(tag: string): React.ElementType {
40
+ let el = byTag.get(tag)
41
+ if (!el) {
42
+ el = make(tag as React.ElementType)
43
+ byTag.set(tag, el)
44
+ }
45
+ return el
46
+ }
47
+
48
+ function forComponent(comp: React.ElementType): React.ElementType {
49
+ let el = byComponent.get(comp)
50
+ if (!el) {
51
+ el = make(comp)
52
+ byComponent.set(comp, el)
53
+ }
54
+ return el
55
+ }
56
+
57
+ /** Props the shim swallows rather than forwarding to the DOM. */
58
+ export type MotionProps = {
59
+ initial?: unknown
60
+ animate?: unknown
61
+ exit?: unknown
62
+ variants?: unknown
63
+ transition?: unknown
64
+ whileHover?: unknown
65
+ whileTap?: unknown
66
+ layout?: unknown
67
+ scrollPosition?: unknown
68
+ }
69
+
70
+ /**
71
+ * `motion.div` has to type as a component taking **the real element's props** plus the motion
72
+ * ones. Typed as a bare `React.ElementType` (or a `Record<string, unknown>` props bag), every
73
+ * handler at a call site becomes an implicit any — that is how the week view's
74
+ * `onPointerDown={(e) => …}` lost its `e`.
75
+ */
76
+ export type MotionComponents = {
77
+ [K in keyof React.JSX.IntrinsicElements]: React.ComponentType<
78
+ React.ComponentProps<K> & MotionProps
79
+ >
80
+ }
81
+
82
+ export const motion = new Proxy(
83
+ { create: (comp: React.ElementType) => forComponent(comp) },
84
+ {
85
+ get(target, key) {
86
+ if (key === "create") return target.create
87
+ if (typeof key === "string") return forTag(key)
88
+ return undefined
89
+ },
90
+ },
91
+ ) as unknown as { create: (comp: React.ElementType) => React.ElementType } & MotionComponents
92
+
93
+ export function AnimatePresence({ children }: { children?: React.ReactNode; initial?: boolean; mode?: string }) {
94
+ return <>{children}</>
95
+ }
96
+
97
+ export type Variants = Record<string, unknown>
@@ -0,0 +1,6 @@
1
+ export function useTheme() {
2
+ return {
3
+ theme: typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light",
4
+ setTheme(_theme: string) {},
5
+ }
6
+ }
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from "react"
2
+
3
+ export type ResizeCallback = (
4
+ _event: unknown,
5
+ direction: string,
6
+ ref: { style: { height: string } },
7
+ ) => void
8
+
9
+ export function Resizable({
10
+ children,
11
+ className,
12
+ }: {
13
+ children?: ReactNode
14
+ className?: string
15
+ minHeight?: number
16
+ maxHeight?: number
17
+ enable?: unknown
18
+ handleStyles?: unknown
19
+ handleClasses?: unknown
20
+ onResizeStart?: unknown
21
+ onResize?: unknown
22
+ onResizeStop?: unknown
23
+ }) {
24
+ return <div className={className}>{children}</div>
25
+ }
@@ -0,0 +1,4 @@
1
+ export const toast = {
2
+ success(_message?: string) {},
3
+ error(_message?: string) {},
4
+ }
@@ -0,0 +1,8 @@
1
+ export type TCalendarView = "day" | "week" | "month" | "year" | "agenda";
2
+ export type TEventColor =
3
+ | "blue"
4
+ | "green"
5
+ | "red"
6
+ | "yellow"
7
+ | "purple"
8
+ | "orange";
@@ -0,0 +1,140 @@
1
+ import { format, parseISO } from "date-fns";
2
+ import type { FC } from "react";
3
+ import { Avatar, AvatarFallback, AvatarImage } from "@monkey-mini-app/ui/components/avatar";
4
+ import {
5
+ Command,
6
+ CommandEmpty,
7
+ CommandGroup,
8
+ CommandInput,
9
+ CommandItem,
10
+ CommandList,
11
+ } from "@monkey-mini-app/ui/components/command";
12
+ import { useDateLocale, useLabels } from "@monkey-mini-app/ui/i18n/context";
13
+ import { cn } from "@monkey-mini-app/ui/lib/utils";
14
+ import { useCalendar } from "../../contexts/calendar-context";
15
+ import { EventDetailsDialog } from "../../dialogs/event-details-dialog";
16
+ import {
17
+ formatTime,
18
+ getBgColor,
19
+ getColorClass,
20
+ getEventsForMonth,
21
+ getFirstLetters,
22
+ toCapitalize,
23
+ } from "../../helpers";
24
+ import { EventBullet } from "../month-view/event-bullet";
25
+
26
+ export const AgendaEvents: FC = () => {
27
+ const {
28
+ events,
29
+ use24HourFormat,
30
+ badgeVariant,
31
+ agendaModeGroupBy,
32
+ selectedDate,
33
+ } = useCalendar();
34
+ const t = useLabels("eventCalendar");
35
+ const dateLocale = useDateLocale();
36
+
37
+ const monthEvents = getEventsForMonth(events, selectedDate);
38
+
39
+ const agendaEvents: Record<string, typeof monthEvents> = {};
40
+ for (const event of monthEvents) {
41
+ const key =
42
+ agendaModeGroupBy === "date"
43
+ ? format(parseISO(event.startDate), "yyyy-MM-dd")
44
+ : event.color;
45
+ (agendaEvents[key] ??= []).push(event);
46
+ }
47
+
48
+ const groupedAndSortedEvents = Object.entries(agendaEvents).sort(
49
+ (a, b) => new Date(a[0]).getTime() - new Date(b[0]).getTime(),
50
+ );
51
+
52
+ return (
53
+ <Command className="py-4 h-[80vh] bg-transparent">
54
+ <div className="mb-4 mx-4">
55
+ <CommandInput placeholder={t.searchAgenda} />
56
+ </div>
57
+ <CommandList className="max-h-max px-3 border-t">
58
+ {groupedAndSortedEvents.map(([date, groupedEvents]) => (
59
+ <CommandGroup
60
+ key={date}
61
+ heading={
62
+ agendaModeGroupBy === "date"
63
+ ? format(parseISO(date), "EEEE, MMMM d, yyyy", { locale: dateLocale })
64
+ : toCapitalize(groupedEvents![0].color)
65
+ }
66
+ >
67
+ {groupedEvents!.map((event) => (
68
+ <CommandItem
69
+ key={event.id}
70
+ className={cn(
71
+ "mb-2 p-4 border rounded-md data-[selected=true]:bg-bg transition-all data-[selected=true]:text-none hover:cursor-pointer",
72
+ {
73
+ [getColorClass(event.color)]: badgeVariant === "colored",
74
+ "hover:bg-zinc-200 dark:hover:bg-gray-900":
75
+ badgeVariant === "dot",
76
+ "hover:opacity-60": badgeVariant === "colored",
77
+ },
78
+ )}
79
+ >
80
+ <EventDetailsDialog event={event}>
81
+ <div className="w-full flex items-center justify-between gap-4">
82
+ <div className="flex items-center gap-2">
83
+ {badgeVariant === "dot" ? (
84
+ <EventBullet color={event.color} />
85
+ ) : (
86
+ <Avatar>
87
+ <AvatarImage src="" alt="@shadcn" />
88
+ <AvatarFallback className={getBgColor(event.color)}>
89
+ {getFirstLetters(event.title)}
90
+ </AvatarFallback>
91
+ </Avatar>
92
+ )}
93
+ <div className="flex flex-col">
94
+ <p
95
+ className={cn({
96
+ "font-medium": badgeVariant === "dot",
97
+ "text-foreground": badgeVariant === "dot",
98
+ })}
99
+ >
100
+ {event.title}
101
+ </p>
102
+ <p className="text-muted-foreground text-sm line-clamp-1 text-ellipsis md:text-clip w-1/3">
103
+ {event.description}
104
+ </p>
105
+ </div>
106
+ </div>
107
+ <div className="w-40 flex justify-center items-center gap-1">
108
+ {agendaModeGroupBy === "date" ? (
109
+ <>
110
+ <p className="text-sm">
111
+ {formatTime(event.startDate, use24HourFormat)}
112
+ </p>
113
+ <span className="text-muted-foreground">-</span>
114
+ <p className="text-sm">
115
+ {formatTime(event.endDate, use24HourFormat)}
116
+ </p>
117
+ </>
118
+ ) : (
119
+ <>
120
+ <p className="text-sm">
121
+ {format(event.startDate, "MM/dd/yyyy")}
122
+ </p>
123
+ <span className="text-sm">at</span>
124
+ <p className="text-sm">
125
+ {formatTime(event.startDate, use24HourFormat)}
126
+ </p>
127
+ </>
128
+ )}
129
+ </div>
130
+ </div>
131
+ </EventDetailsDialog>
132
+ </CommandItem>
133
+ ))}
134
+ </CommandGroup>
135
+ ))}
136
+ <CommandEmpty>{t.noEvents}</CommandEmpty>
137
+ </CommandList>
138
+ </Command>
139
+ );
140
+ };
@@ -0,0 +1,69 @@
1
+ import { motion } from "../../shims/motion";
2
+ import { useMemo } from "react";
3
+ import {
4
+ staggerContainer,
5
+ transition,
6
+ } from "../../animations";
7
+ import { useLabels } from "@monkey-mini-app/ui/i18n/context";
8
+ import { useCalendar } from "../../contexts/calendar-context";
9
+
10
+ import {
11
+ calculateMonthEventPositions,
12
+ getCalendarCells,
13
+ } from "../../helpers";
14
+
15
+ import type { IEvent } from "../../interfaces";
16
+ import { DayCell } from "./day-cell";
17
+
18
+ interface IProps {
19
+ singleDayEvents: IEvent[];
20
+ multiDayEvents: IEvent[];
21
+ }
22
+
23
+ export function CalendarMonthView({ singleDayEvents, multiDayEvents }: IProps) {
24
+ const { selectedDate } = useCalendar();
25
+ const t = useLabels("eventCalendar");
26
+
27
+ const allEvents = [...multiDayEvents, ...singleDayEvents];
28
+
29
+ const cells = useMemo(() => getCalendarCells(selectedDate), [selectedDate]);
30
+
31
+ const eventPositions = useMemo(
32
+ () =>
33
+ calculateMonthEventPositions(
34
+ multiDayEvents,
35
+ singleDayEvents,
36
+ selectedDate,
37
+ ),
38
+ [multiDayEvents, singleDayEvents, selectedDate],
39
+ );
40
+
41
+ return (
42
+ <motion.div initial="initial" animate="animate" variants={staggerContainer}>
43
+ <div className="grid grid-cols-7">
44
+ {t.weekdaysSun.map((day, index) => (
45
+ <motion.div
46
+ key={day}
47
+ className="flex items-center justify-center py-2"
48
+ initial={{ opacity: 0, y: -10 }}
49
+ animate={{ opacity: 1, y: 0 }}
50
+ transition={{ delay: index * 0.05, ...transition }}
51
+ >
52
+ <span className="text-xs font-medium text-t-quaternary">{day}</span>
53
+ </motion.div>
54
+ ))}
55
+ </div>
56
+
57
+ <div className="grid grid-cols-7 overflow-hidden">
58
+ {cells.map((cell) => (
59
+ <DayCell
60
+ key={cell.date.toISOString()}
61
+ cell={cell}
62
+ events={allEvents}
63
+ eventPositions={eventPositions}
64
+ />
65
+ ))}
66
+ </div>
67
+ </motion.div>
68
+ );
69
+ }
@@ -0,0 +1,201 @@
1
+ "use client";
2
+
3
+ import { cva } from "class-variance-authority";
4
+ import { isToday, startOfDay, isSunday, isSameMonth } from "date-fns";
5
+ import { motion } from "../../shims/motion";
6
+ import { useMemo, useCallback } from "react";
7
+
8
+ import { cn } from "@monkey-mini-app/ui/lib/utils";
9
+ import { transition } from "../../animations";
10
+ import { EventListDialog } from "../../dialogs/events-list-dialog";
11
+ import { DroppableArea } from "../../dnd/droppable-area";
12
+ import { getMonthCellEvents } from "../../helpers";
13
+ import { useMediaQuery } from "../../hooks";
14
+ import type { ICalendarCell, IEvent } from "../../interfaces";
15
+ import { EventBullet } from "./event-bullet";
16
+ import { MonthEventBadge } from "./month-event-badge";
17
+ import { Button } from "@monkey-mini-app/ui/components/button";
18
+ import { Plus } from "lucide-react";
19
+ import { AddEditEventDialog } from "../../dialogs/add-edit-event-dialog";
20
+
21
+ interface IProps {
22
+ cell: ICalendarCell;
23
+ events: IEvent[];
24
+ eventPositions: Record<string, number>;
25
+ }
26
+
27
+ export const dayCellVariants = cva("text-white", {
28
+ variants: {
29
+ color: {
30
+ blue: "bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-400 ",
31
+ green:
32
+ "bg-green-600 dark:bg-green-500 hover:bg-green-700 dark:hover:bg-green-400",
33
+ red: "bg-red-600 dark:bg-red-500 hover:bg-red-700 dark:hover:bg-red-400",
34
+ yellow:
35
+ "bg-yellow-600 dark:bg-yellow-500 hover:bg-yellow-700 dark:hover:bg-yellow-400",
36
+ purple:
37
+ "bg-purple-600 dark:bg-purple-500 hover:bg-purple-700 dark:hover:bg-purple-400",
38
+ orange:
39
+ "bg-orange-600 dark:bg-orange-500 hover:bg-orange-700 dark:hover:bg-orange-400",
40
+ gray: "bg-gray-600 dark:bg-gray-500 hover:bg-gray-700 dark:hover:bg-gray-400",
41
+ },
42
+ },
43
+ defaultVariants: {
44
+ color: "blue",
45
+ },
46
+ });
47
+
48
+ const MAX_VISIBLE_EVENTS = 3;
49
+
50
+ export function DayCell({ cell, events, eventPositions }: IProps) {
51
+ const { day, currentMonth, date } = cell;
52
+ const isMobile = useMediaQuery("(max-width: 768px)");
53
+
54
+ // Memoize cellEvents and currentCellMonth for performance
55
+ const { cellEvents, currentCellMonth } = useMemo(() => {
56
+ const cellEvents = getMonthCellEvents(date, events, eventPositions);
57
+ const currentCellMonth = startOfDay(
58
+ new Date(date.getFullYear(), date.getMonth(), 1),
59
+ );
60
+ return { cellEvents, currentCellMonth };
61
+ }, [date, events, eventPositions]);
62
+
63
+ // Memoize event rendering for each position with animation
64
+ const renderEventAtPosition = useCallback(
65
+ (position: number) => {
66
+ const event = cellEvents.find((e) => e.position === position);
67
+ if (!event) {
68
+ return (
69
+ <motion.div
70
+ key={`empty-${position}`}
71
+ className="lg:flex-1"
72
+ initial={false}
73
+ animate={false}
74
+ />
75
+ );
76
+ }
77
+ const showBullet = isSameMonth(
78
+ new Date(event.startDate),
79
+ currentCellMonth,
80
+ );
81
+
82
+ return (
83
+ <motion.div
84
+ key={`event-${event.id}-${position}`}
85
+ className="lg:flex-1"
86
+ initial={{ opacity: 0, x: -10 }}
87
+ animate={{ opacity: 1, x: 0 }}
88
+ transition={{ delay: position * 0.1, ...transition }}
89
+ >
90
+ {showBullet && (
91
+ <EventBullet className="lg:hidden" color={event.color} />
92
+ )}
93
+ <MonthEventBadge
94
+ className="hidden lg:flex"
95
+ event={event}
96
+ cellDate={startOfDay(date)}
97
+ />
98
+ </motion.div>
99
+ );
100
+ },
101
+ [cellEvents, currentCellMonth, date],
102
+ );
103
+
104
+ const showMoreCount = cellEvents.length - MAX_VISIBLE_EVENTS;
105
+
106
+ const showMobileMore = isMobile && currentMonth && showMoreCount > 0;
107
+ const showDesktopMore = !isMobile && currentMonth && showMoreCount > 0;
108
+
109
+ const cellContent = useMemo(
110
+ () => (
111
+ <motion.div
112
+ className={cn(
113
+ "flex h-full lg:min-h-40 flex-col gap-1 border-l border-t",
114
+ isSunday(date) && "border-l-0",
115
+ )}
116
+ initial={{ opacity: 0, y: 10 }}
117
+ animate={{ opacity: 1, y: 0 }}
118
+ transition={transition}
119
+ >
120
+ <DroppableArea date={date} className="w-full h-full py-2">
121
+ <motion.span
122
+ className={cn(
123
+ "h-6 px-1 text-xs font-semibold lg:px-2",
124
+ !currentMonth && "opacity-20",
125
+ isToday(date) &&
126
+ "flex w-6 translate-x-1 items-center justify-center rounded-full bg-primary px-0 font-bold text-primary-foreground",
127
+ )}
128
+ >
129
+ {day}
130
+ </motion.span>
131
+
132
+ <motion.div
133
+ className={cn(
134
+ "flex h-fit gap-1 px-2 mt-1 lg:h-[94px] lg:flex-col lg:gap-2 lg:px-0",
135
+ !currentMonth && "opacity-50",
136
+ )}
137
+ >
138
+ {cellEvents.length === 0 && !isMobile ? (
139
+ <div className="w-full h-full flex justify-center items-center group">
140
+ <AddEditEventDialog startDate={date}>
141
+ <Button
142
+ variant="ghost"
143
+ className="border opacity-0 group-hover:opacity-100 transition-opacity duration-200"
144
+ >
145
+ <Plus className="h-4 w-4" />
146
+ <span className="max-sm:hidden">Add Event</span>
147
+ </Button>
148
+ </AddEditEventDialog>
149
+ </div>
150
+ ) : (
151
+ [0, 1, 2].map(renderEventAtPosition)
152
+ )}
153
+ </motion.div>
154
+
155
+ {showMobileMore && (
156
+ <div className="flex justify-end items-end mx-2">
157
+ <span className="text-[0.6rem] font-semibold text-accent-foreground">
158
+ +{showMoreCount}
159
+ </span>
160
+ </div>
161
+ )}
162
+
163
+ {showDesktopMore && (
164
+ <motion.div
165
+ className={cn(
166
+ "h-4.5 px-1.5 my-2 text-end text-xs font-semibold text-muted-foreground",
167
+ !currentMonth && "opacity-50",
168
+ )}
169
+ initial={{ opacity: 0, y: 5 }}
170
+ animate={{ opacity: 1, y: 0 }}
171
+ transition={{ delay: 0.3, ...transition }}
172
+ >
173
+ <EventListDialog date={date} events={cellEvents} />
174
+ </motion.div>
175
+ )}
176
+ </DroppableArea>
177
+ </motion.div>
178
+ ),
179
+ [
180
+ date,
181
+ day,
182
+ currentMonth,
183
+ cellEvents,
184
+ showMobileMore,
185
+ showDesktopMore,
186
+ showMoreCount,
187
+ renderEventAtPosition,
188
+ isMobile,
189
+ ],
190
+ );
191
+
192
+ if (isMobile && currentMonth) {
193
+ return (
194
+ <EventListDialog date={date} events={cellEvents}>
195
+ {cellContent}
196
+ </EventListDialog>
197
+ );
198
+ }
199
+
200
+ return cellContent;
201
+ }