@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.
- package/README.md +58 -0
- package/dist/globals.css +1 -1
- package/dist/index.js +12 -2
- package/dist/manifest.json +6 -6
- package/dist/runtime.js +9 -0
- package/dist/sdk.js +158 -0
- package/dist/src/blocks/activity-feed.tsx +7 -0
- package/dist/src/blocks/app-shell.tsx +1 -0
- package/dist/src/blocks/attachment-gallery.tsx +7 -0
- package/dist/src/blocks/bar-chart.tsx +15 -1
- package/dist/src/blocks/comment-thread.tsx +7 -0
- package/dist/src/blocks/dashboard-shell.tsx +109 -0
- package/dist/src/blocks/description-list.tsx +7 -0
- package/dist/src/blocks/detail-panel.tsx +1 -0
- package/dist/src/blocks/donut-chart.tsx +19 -2
- package/dist/src/blocks/env-badge.tsx +7 -0
- package/dist/src/blocks/env-table.tsx +316 -0
- package/dist/src/blocks/file-tree.tsx +7 -0
- package/dist/src/blocks/filter-bar.tsx +1 -0
- package/dist/src/blocks/form-sheet.tsx +160 -0
- package/dist/src/blocks/gauge.tsx +29 -6
- package/dist/src/blocks/list-detail.tsx +121 -0
- package/dist/src/blocks/notification-center.tsx +7 -0
- package/dist/src/blocks/page-header.tsx +1 -0
- package/dist/src/blocks/progress-ring.tsx +20 -2
- package/dist/src/blocks/radar-chart.tsx +18 -1
- package/dist/src/blocks/request-inspector.tsx +7 -0
- package/dist/src/blocks/reveal.tsx +56 -0
- package/dist/src/blocks/run-timeline.tsx +7 -0
- package/dist/src/blocks/scrollspy.tsx +7 -0
- package/dist/src/blocks/settings-split.tsx +154 -0
- package/dist/src/blocks/severity-chip.tsx +7 -0
- package/dist/src/blocks/sparkline.tsx +12 -3
- package/dist/src/blocks/stat-card.tsx +1 -0
- package/dist/src/blocks/status-badge.tsx +1 -0
- package/dist/src/blocks/table-page.tsx +83 -0
- package/dist/src/blocks/terminal.tsx +21 -3
- package/dist/src/blocks/test-step-list.tsx +7 -0
- package/dist/src/blocks/trend-card.tsx +7 -0
- package/dist/src/blocks/wizard-shell.tsx +132 -0
- package/dist/src/cdn-modules.d.ts +5 -0
- package/dist/src/components/accordion.tsx +7 -0
- package/dist/src/components/alert-dialog.tsx +7 -0
- package/dist/src/components/alert.tsx +7 -0
- package/dist/src/components/aspect-ratio.tsx +4 -0
- package/dist/src/components/attachment.tsx +4 -0
- package/dist/src/components/avatar.tsx +4 -0
- package/dist/src/components/badge.tsx +7 -0
- package/dist/src/components/breadcrumb.tsx +4 -0
- package/dist/src/components/bubble.tsx +4 -0
- package/dist/src/components/button-group.tsx +6 -0
- package/dist/src/components/button.tsx +7 -0
- package/dist/src/components/calendar.tsx +4 -0
- package/dist/src/components/card.tsx +8 -1
- package/dist/src/components/chart.tsx +7 -0
- package/dist/src/components/checkbox.tsx +5 -0
- package/dist/src/components/collapsible.tsx +4 -0
- package/dist/src/components/combobox.tsx +4 -0
- package/dist/src/components/command.tsx +6 -0
- package/dist/src/components/context-menu.tsx +4 -0
- package/dist/src/components/dialog.tsx +21 -0
- package/dist/src/components/drawer.tsx +4 -0
- package/dist/src/components/dropdown-menu.tsx +7 -0
- package/dist/src/components/empty.tsx +7 -0
- package/dist/src/components/field.tsx +6 -0
- package/dist/src/components/hover-card.tsx +4 -0
- package/dist/src/components/input-group.tsx +4 -0
- package/dist/src/components/input.tsx +7 -0
- package/dist/src/components/item.tsx +4 -0
- package/dist/src/components/kbd.tsx +4 -0
- package/dist/src/components/label.tsx +7 -0
- package/dist/src/components/marker.tsx +4 -0
- package/dist/src/components/menubar.tsx +4 -0
- package/dist/src/components/message-scroller.tsx +4 -0
- package/dist/src/components/message.tsx +4 -0
- package/dist/src/components/native-select.tsx +4 -0
- package/dist/src/components/navigation-menu.tsx +4 -0
- package/dist/src/components/pagination.tsx +4 -0
- package/dist/src/components/popover.tsx +7 -0
- package/dist/src/components/progress.tsx +7 -0
- package/dist/src/components/questionnaire.tsx +4 -0
- package/dist/src/components/radio-group.tsx +4 -0
- package/dist/src/components/resizable.tsx +7 -0
- package/dist/src/components/scroll-area.tsx +4 -0
- package/dist/src/components/select.tsx +7 -0
- package/dist/src/components/separator.tsx +5 -0
- package/dist/src/components/sheet.tsx +22 -0
- package/dist/src/components/sidebar.tsx +4 -0
- package/dist/src/components/skeleton.tsx +7 -0
- package/dist/src/components/slider.tsx +4 -0
- package/dist/src/components/spinner.tsx +6 -0
- package/dist/src/components/switch.tsx +7 -0
- package/dist/src/components/table.tsx +7 -0
- package/dist/src/components/tabs.tsx +7 -0
- package/dist/src/components/textarea.tsx +7 -0
- package/dist/src/components/toast.tsx +7 -0
- package/dist/src/components/toggle-group.tsx +4 -0
- package/dist/src/components/toggle.tsx +4 -0
- package/dist/src/components/tooltip.tsx +7 -0
- package/dist/src/composites/autocomplete.tsx +7 -0
- package/dist/src/composites/cascader.tsx +7 -0
- package/dist/src/composites/color-picker.tsx +7 -0
- package/dist/src/composites/confirm-dialog.tsx +7 -0
- package/dist/src/composites/copyable.tsx +9 -1
- package/dist/src/composites/currency-input.tsx +7 -0
- package/dist/src/composites/date-picker.tsx +7 -0
- package/dist/src/composites/date-range-picker.tsx +4 -0
- package/dist/src/composites/date-time-picker.tsx +7 -0
- package/dist/src/composites/date-time-range-picker.tsx +7 -0
- package/dist/src/composites/duration-input.tsx +7 -0
- package/dist/src/composites/mini-calendar.tsx +7 -0
- package/dist/src/composites/number-field.tsx +7 -0
- package/dist/src/composites/password-field.tsx +7 -0
- package/dist/src/composites/phone-input.tsx +7 -0
- package/dist/src/composites/rating.tsx +7 -0
- package/dist/src/composites/relative-date-picker.tsx +7 -0
- package/dist/src/composites/search-input.tsx +7 -0
- package/dist/src/composites/slider-range.tsx +7 -0
- package/dist/src/composites/tag-input.tsx +7 -0
- package/dist/src/composites/time-picker.tsx +11 -1
- package/dist/src/composites/time-range-picker.tsx +7 -0
- package/dist/src/composites/timezone-select.tsx +7 -0
- package/dist/src/composites/transfer.tsx +7 -0
- package/dist/src/composites/user-picker.tsx +7 -0
- package/dist/src/hooks/use-html-dark.ts +37 -0
- package/dist/src/hooks/use-mobile.ts +3 -0
- package/dist/src/i18n/en.ts +109 -2
- package/dist/src/i18n/zh.ts +99 -2
- package/dist/src/index.ts +147 -134
- package/dist/src/lib/clipboard.ts +10 -0
- package/dist/src/lib/illustrations.tsx +2 -2
- package/dist/src/products/code-block.tsx +35 -18
- package/dist/src/products/code-editor.tsx +156 -24
- package/dist/src/products/commit-graph.tsx +644 -0
- package/dist/src/products/data-grid.tsx +2 -1
- package/dist/src/products/diff-viewer.tsx +104 -8
- package/dist/src/products/event-calendar.tsx +28 -823
- package/dist/src/products/file-dropzone.tsx +7 -0
- package/dist/src/products/full-calendar/animations.ts +38 -0
- package/dist/src/products/full-calendar/calendar-body.tsx +77 -0
- package/dist/src/products/full-calendar/constants.ts +10 -0
- package/dist/src/products/full-calendar/contexts/calendar-context.tsx +255 -0
- package/dist/src/products/full-calendar/contexts/dnd-context.tsx +157 -0
- package/dist/src/products/full-calendar/dialogs/add-edit-event-dialog.tsx +187 -0
- package/dist/src/products/full-calendar/dialogs/delete-event-dialog.tsx +39 -0
- package/dist/src/products/full-calendar/dialogs/event-details-dialog.tsx +100 -0
- package/dist/src/products/full-calendar/dialogs/events-list-dialog.tsx +87 -0
- package/dist/src/products/full-calendar/dnd/draggable-event.tsx +45 -0
- package/dist/src/products/full-calendar/dnd/droppable-area.tsx +45 -0
- package/dist/src/products/full-calendar/dnd/resizable-event.tsx +179 -0
- package/dist/src/products/full-calendar/header/calendar-header.tsx +65 -0
- package/dist/src/products/full-calendar/header/date-navigator.tsx +110 -0
- package/dist/src/products/full-calendar/header/filter.tsx +58 -0
- package/dist/src/products/full-calendar/header/today-button.tsx +45 -0
- package/dist/src/products/full-calendar/header/user-select.tsx +64 -0
- package/dist/src/products/full-calendar/header/view-tabs.tsx +36 -0
- package/dist/src/products/full-calendar/helpers.ts +453 -0
- package/dist/src/products/full-calendar/hooks/use-time-range-create.ts +152 -0
- package/dist/src/products/full-calendar/hooks.ts +70 -0
- package/dist/src/products/full-calendar/interfaces.ts +23 -0
- package/dist/src/products/full-calendar/settings/settings.tsx +80 -0
- package/dist/src/products/full-calendar/shims/avatar-group.tsx +16 -0
- package/dist/src/products/full-calendar/shims/day-picker.tsx +1 -0
- package/dist/src/products/full-calendar/shims/motion.tsx +97 -0
- package/dist/src/products/full-calendar/shims/next-themes.ts +6 -0
- package/dist/src/products/full-calendar/shims/re-resizable.tsx +25 -0
- package/dist/src/products/full-calendar/shims/sonner.ts +4 -0
- package/dist/src/products/full-calendar/types.ts +8 -0
- package/dist/src/products/full-calendar/views/agenda-view/agenda-events.tsx +140 -0
- package/dist/src/products/full-calendar/views/month-view/calendar-month-view.tsx +69 -0
- package/dist/src/products/full-calendar/views/month-view/day-cell.tsx +201 -0
- package/dist/src/products/full-calendar/views/month-view/event-bullet.tsx +40 -0
- package/dist/src/products/full-calendar/views/month-view/month-event-badge.tsx +143 -0
- package/dist/src/products/full-calendar/views/week-and-day-view/calendar-day-view.tsx +295 -0
- package/dist/src/products/full-calendar/views/week-and-day-view/calendar-time-line.tsx +35 -0
- package/dist/src/products/full-calendar/views/week-and-day-view/calendar-week-view.tsx +253 -0
- package/dist/src/products/full-calendar/views/week-and-day-view/day-view-multi-day-events-row.tsx +73 -0
- package/dist/src/products/full-calendar/views/week-and-day-view/event-block.tsx +114 -0
- package/dist/src/products/full-calendar/views/week-and-day-view/render-grouped-events.tsx +49 -0
- package/dist/src/products/full-calendar/views/week-and-day-view/week-view-multi-day-events-row.tsx +143 -0
- package/dist/src/products/full-calendar/views/year-view/calendar-year-view.tsx +161 -0
- package/dist/src/products/gantt.tsx +7 -0
- package/dist/src/products/jira-wiki.tsx +1 -0
- package/dist/src/products/jql-input.tsx +340 -37
- package/dist/src/products/json-viewer.tsx +7 -0
- package/dist/src/products/kanban-issue-panel.tsx +7 -0
- package/dist/src/products/kanban.tsx +1 -0
- package/dist/src/products/log-viewer.tsx +390 -18
- package/dist/src/products/markdown-editor.tsx +21 -6
- package/dist/src/products/markdown.tsx +7 -0
- package/dist/src/products/rich-text-editor.tsx +65 -59
- package/dist/src/products/sortable-list.tsx +7 -0
- package/dist/src/products/stepper.tsx +219 -70
- package/dist/src/products/timeline.tsx +7 -0
- package/dist/src/products/tree-view.tsx +7 -0
- package/dist/src/sdk/app-error-boundary.tsx +240 -0
- package/dist/src/sdk/app-id.ts +7 -0
- package/dist/src/sdk/use-app.ts +126 -0
- package/dist/src/styles/globals.css +7 -7
- package/dist/vendors/lodash.js +20 -0
- package/dist/vendors/motion.js +12 -0
- package/package.json +23 -19
- package/dist/src/products/jql-language.ts +0 -70
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps, CSSProperties, ReactNode } from "react";
|
|
4
|
+
import type { UiMessages } from "@monkey-mini-app/ui/i18n/en";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Avatar,
|
|
8
|
+
AvatarFallback,
|
|
9
|
+
AvatarImage,
|
|
10
|
+
} from "@monkey-mini-app/ui/components/avatar";
|
|
11
|
+
import {
|
|
12
|
+
Popover,
|
|
13
|
+
PopoverContent,
|
|
14
|
+
PopoverTrigger,
|
|
15
|
+
} from "@monkey-mini-app/ui/components/popover";
|
|
16
|
+
import { useDateLocale, useLabels } from "@monkey-mini-app/ui/i18n/context";
|
|
17
|
+
import { cn } from "@monkey-mini-app/ui/lib/utils";
|
|
18
|
+
|
|
19
|
+
export type CommitAuthor = {
|
|
20
|
+
name: string;
|
|
21
|
+
avatarUrl?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type Commit = {
|
|
25
|
+
/** Commit hash (full or abbreviated). */
|
|
26
|
+
hash: string;
|
|
27
|
+
/** Commit message (first line). */
|
|
28
|
+
message: string;
|
|
29
|
+
/** Commit author. */
|
|
30
|
+
author: CommitAuthor;
|
|
31
|
+
/** ISO date string or Date object. */
|
|
32
|
+
date: string | Date;
|
|
33
|
+
/** Parent commit hashes. Empty for root commits. Two parents = merge commit. */
|
|
34
|
+
parents: string[];
|
|
35
|
+
/** Branch or ref label (e.g. "main", "feat/auth"). */
|
|
36
|
+
refs?: string[];
|
|
37
|
+
/** Tag label (e.g. "v1.0.0"). */
|
|
38
|
+
tag?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type CommitGraphProps = Omit<ComponentProps<"div">, "children"> & {
|
|
42
|
+
/** Commits in topological order (newest first). Each commit includes parent hashes. */
|
|
43
|
+
commits: Commit[];
|
|
44
|
+
/** Number of hash characters to display. @default 7 */
|
|
45
|
+
truncateHash?: number;
|
|
46
|
+
/** Pixel width per rail column. @default 24 */
|
|
47
|
+
railWidth?: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const RAIL_COLORS = [
|
|
51
|
+
"#3b82f6",
|
|
52
|
+
"#22c55e",
|
|
53
|
+
"#f59e0b",
|
|
54
|
+
"#ef4444",
|
|
55
|
+
"#8b5cf6",
|
|
56
|
+
"#ec4899",
|
|
57
|
+
"#06b6d4",
|
|
58
|
+
"#f97316",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function color(rail: number): string {
|
|
62
|
+
return RAIL_COLORS[rail % RAIL_COLORS.length] ?? "#3b82f6";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function initials(name: string): string {
|
|
66
|
+
return name
|
|
67
|
+
.split(/\s+/)
|
|
68
|
+
.map((w) => w[0] ?? "")
|
|
69
|
+
.join("")
|
|
70
|
+
.toUpperCase()
|
|
71
|
+
.slice(0, 2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Graph layout computation
|
|
75
|
+
|
|
76
|
+
type GraphRow = {
|
|
77
|
+
commit: Commit;
|
|
78
|
+
rail: number;
|
|
79
|
+
rails: (string | null)[];
|
|
80
|
+
edges: Edge[];
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type Edge = {
|
|
84
|
+
fromRail: number;
|
|
85
|
+
toRail: number;
|
|
86
|
+
color: string;
|
|
87
|
+
type: "straight" | "merge-in" | "fork-out";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function computeLayout(commits: Commit[]): GraphRow[] {
|
|
91
|
+
const rows: GraphRow[] = [];
|
|
92
|
+
// Active rails: each slot holds the hash of the commit it's "waiting for"
|
|
93
|
+
const rails: (string | null)[] = [];
|
|
94
|
+
|
|
95
|
+
for (const commit of commits) {
|
|
96
|
+
const hash = commit.hash;
|
|
97
|
+
|
|
98
|
+
// Find which rail this commit occupies (if any rail is waiting for it)
|
|
99
|
+
let commitRail = rails.indexOf(hash);
|
|
100
|
+
|
|
101
|
+
if (commitRail === -1) {
|
|
102
|
+
// New branch — find first empty slot or append
|
|
103
|
+
const emptySlot = rails.indexOf(null);
|
|
104
|
+
if (emptySlot !== -1) {
|
|
105
|
+
commitRail = emptySlot;
|
|
106
|
+
rails[commitRail] = hash;
|
|
107
|
+
} else {
|
|
108
|
+
commitRail = rails.length;
|
|
109
|
+
rails.push(hash);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const commitColor = color(commitRail);
|
|
114
|
+
const edges: Edge[] = [];
|
|
115
|
+
|
|
116
|
+
// Draw straight lines for all other active rails (pass-through)
|
|
117
|
+
for (let r = 0; r < rails.length; r++) {
|
|
118
|
+
if (r !== commitRail && rails[r] !== null) {
|
|
119
|
+
edges.push({
|
|
120
|
+
fromRail: r,
|
|
121
|
+
toRail: r,
|
|
122
|
+
color: color(r),
|
|
123
|
+
type: "straight",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Clear this rail — the commit has been rendered
|
|
129
|
+
rails[commitRail] = null;
|
|
130
|
+
|
|
131
|
+
// Process parents
|
|
132
|
+
const parents = commit.parents;
|
|
133
|
+
const firstParent = parents[0];
|
|
134
|
+
if (firstParent !== undefined) {
|
|
135
|
+
// First parent continues on the same rail
|
|
136
|
+
const existingRail = rails.indexOf(firstParent);
|
|
137
|
+
if (existingRail !== -1) {
|
|
138
|
+
// Parent already expected on another rail — merge line
|
|
139
|
+
edges.push({
|
|
140
|
+
fromRail: commitRail,
|
|
141
|
+
toRail: existingRail,
|
|
142
|
+
color: commitColor,
|
|
143
|
+
type: "merge-in",
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
// Parent takes this commit's rail
|
|
147
|
+
rails[commitRail] = firstParent;
|
|
148
|
+
edges.push({
|
|
149
|
+
fromRail: commitRail,
|
|
150
|
+
toRail: commitRail,
|
|
151
|
+
color: commitColor,
|
|
152
|
+
type: "straight",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Second+ parents (merge sources)
|
|
158
|
+
for (let p = 1; p < parents.length; p++) {
|
|
159
|
+
const parentHash = parents[p];
|
|
160
|
+
if (parentHash === undefined) continue;
|
|
161
|
+
const existingRail = rails.indexOf(parentHash);
|
|
162
|
+
if (existingRail !== -1) {
|
|
163
|
+
// Already on a rail — draw merge line from that rail
|
|
164
|
+
edges.push({
|
|
165
|
+
fromRail: existingRail,
|
|
166
|
+
toRail: commitRail,
|
|
167
|
+
color: color(existingRail),
|
|
168
|
+
type: "merge-in",
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
// Needs a new rail — fork out
|
|
172
|
+
const emptySlot = rails.indexOf(null);
|
|
173
|
+
const newRail = emptySlot !== -1 ? emptySlot : rails.length;
|
|
174
|
+
if (newRail >= rails.length) rails.push(null);
|
|
175
|
+
rails[newRail] = parentHash;
|
|
176
|
+
edges.push({
|
|
177
|
+
fromRail: commitRail,
|
|
178
|
+
toRail: newRail,
|
|
179
|
+
color: color(newRail),
|
|
180
|
+
type: "fork-out",
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Trim trailing nulls
|
|
186
|
+
while (rails.length > 0 && rails[rails.length - 1] === null) {
|
|
187
|
+
rails.pop();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
rows.push({
|
|
191
|
+
commit,
|
|
192
|
+
rail: commitRail,
|
|
193
|
+
rails: [...rails],
|
|
194
|
+
edges,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return rows;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// SVG rendering for rails
|
|
202
|
+
|
|
203
|
+
const ROW_HEIGHT = 40;
|
|
204
|
+
|
|
205
|
+
function RailsSVG({
|
|
206
|
+
row,
|
|
207
|
+
prevRow,
|
|
208
|
+
railWidth,
|
|
209
|
+
maxRails,
|
|
210
|
+
}: {
|
|
211
|
+
row: GraphRow;
|
|
212
|
+
prevRow: GraphRow | null;
|
|
213
|
+
railWidth: number;
|
|
214
|
+
maxRails: number;
|
|
215
|
+
}) {
|
|
216
|
+
const w = maxRails * railWidth;
|
|
217
|
+
const h = ROW_HEIGHT;
|
|
218
|
+
const cy = h / 2;
|
|
219
|
+
|
|
220
|
+
function rx(rail: number) {
|
|
221
|
+
return rail * railWidth + railWidth / 2;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const commitX = rx(row.rail);
|
|
225
|
+
|
|
226
|
+
// Collect which rails are active above this row (from previous row's post-state)
|
|
227
|
+
const activeAbove = new Set<number>();
|
|
228
|
+
if (prevRow) {
|
|
229
|
+
for (let r = 0; r < prevRow.rails.length; r++) {
|
|
230
|
+
if (prevRow.rails[r] !== null) activeAbove.add(r);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Collect which rails are active below this row
|
|
235
|
+
const activeBelow = new Set<number>();
|
|
236
|
+
for (let r = 0; r < row.rails.length; r++) {
|
|
237
|
+
if (row.rails[r] !== null) activeBelow.add(r);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<svg
|
|
242
|
+
width={w}
|
|
243
|
+
height={h}
|
|
244
|
+
viewBox={`0 0 ${w} ${h}`}
|
|
245
|
+
className="shrink-0"
|
|
246
|
+
aria-hidden="true"
|
|
247
|
+
>
|
|
248
|
+
{/* Pass-through rails */}
|
|
249
|
+
{Array.from(activeAbove).map((r) => {
|
|
250
|
+
if (r === row.rail) return null;
|
|
251
|
+
if (!activeBelow.has(r)) return null;
|
|
252
|
+
const x = rx(r);
|
|
253
|
+
return (
|
|
254
|
+
<line
|
|
255
|
+
key={`pt-${r}`}
|
|
256
|
+
x1={x}
|
|
257
|
+
y1={0}
|
|
258
|
+
x2={x}
|
|
259
|
+
y2={h}
|
|
260
|
+
stroke={color(r)}
|
|
261
|
+
strokeWidth={2}
|
|
262
|
+
strokeOpacity={0.6}
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
})}
|
|
266
|
+
|
|
267
|
+
{/* Commit rail: incoming line (top to dot) */}
|
|
268
|
+
{activeAbove.has(row.rail) && (
|
|
269
|
+
<line
|
|
270
|
+
x1={commitX}
|
|
271
|
+
y1={0}
|
|
272
|
+
x2={commitX}
|
|
273
|
+
y2={cy}
|
|
274
|
+
stroke={color(row.rail)}
|
|
275
|
+
strokeWidth={2}
|
|
276
|
+
strokeOpacity={0.6}
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{/* Commit rail: outgoing line (dot to bottom) */}
|
|
281
|
+
{activeBelow.has(row.rail) && (
|
|
282
|
+
<line
|
|
283
|
+
x1={commitX}
|
|
284
|
+
y1={cy}
|
|
285
|
+
x2={commitX}
|
|
286
|
+
y2={h}
|
|
287
|
+
stroke={color(row.rail)}
|
|
288
|
+
strokeWidth={2}
|
|
289
|
+
strokeOpacity={0.6}
|
|
290
|
+
/>
|
|
291
|
+
)}
|
|
292
|
+
|
|
293
|
+
{/* Fork-out curves */}
|
|
294
|
+
{row.edges
|
|
295
|
+
.filter((e) => e.type === "fork-out")
|
|
296
|
+
.map((edge) => (
|
|
297
|
+
<path
|
|
298
|
+
key={`f-${edge.fromRail}-${edge.toRail}`}
|
|
299
|
+
d={`M${rx(edge.fromRail)},${cy} C${rx(edge.fromRail)},${h} ${rx(edge.toRail)},${cy} ${rx(edge.toRail)},${h}`}
|
|
300
|
+
stroke={edge.color}
|
|
301
|
+
strokeWidth={2}
|
|
302
|
+
strokeOpacity={0.6}
|
|
303
|
+
fill="none"
|
|
304
|
+
/>
|
|
305
|
+
))}
|
|
306
|
+
|
|
307
|
+
{/* Merge curves */}
|
|
308
|
+
{row.edges
|
|
309
|
+
.filter((e) => e.type === "merge-in")
|
|
310
|
+
.map((edge) => {
|
|
311
|
+
const isOutgoing = edge.fromRail === row.rail;
|
|
312
|
+
const x1 = rx(edge.fromRail);
|
|
313
|
+
const x2 = rx(edge.toRail);
|
|
314
|
+
const d = isOutgoing
|
|
315
|
+
? `M${x1},${cy} C${x1},${h} ${x2},${cy} ${x2},${h}`
|
|
316
|
+
: `M${x1},${0} C${x1},${cy} ${x2},${0} ${x2},${cy}`;
|
|
317
|
+
return (
|
|
318
|
+
<path
|
|
319
|
+
key={`m-${edge.fromRail}-${edge.toRail}-${isOutgoing ? "out" : "in"}`}
|
|
320
|
+
d={d}
|
|
321
|
+
stroke={edge.color}
|
|
322
|
+
strokeWidth={2}
|
|
323
|
+
strokeOpacity={0.6}
|
|
324
|
+
fill="none"
|
|
325
|
+
/>
|
|
326
|
+
);
|
|
327
|
+
})}
|
|
328
|
+
|
|
329
|
+
{/* Rails that terminate here */}
|
|
330
|
+
{Array.from(activeAbove).map((r) => {
|
|
331
|
+
if (r === row.rail) return null;
|
|
332
|
+
if (activeBelow.has(r)) return null;
|
|
333
|
+
const x = rx(r);
|
|
334
|
+
return (
|
|
335
|
+
<line
|
|
336
|
+
key={`end-${r}`}
|
|
337
|
+
x1={x}
|
|
338
|
+
y1={0}
|
|
339
|
+
x2={x}
|
|
340
|
+
y2={cy}
|
|
341
|
+
stroke={color(r)}
|
|
342
|
+
strokeWidth={2}
|
|
343
|
+
strokeOpacity={0.6}
|
|
344
|
+
/>
|
|
345
|
+
);
|
|
346
|
+
})}
|
|
347
|
+
|
|
348
|
+
{/* Commit dot */}
|
|
349
|
+
<circle
|
|
350
|
+
cx={commitX}
|
|
351
|
+
cy={cy}
|
|
352
|
+
r={5}
|
|
353
|
+
fill={color(row.rail)}
|
|
354
|
+
stroke="var(--color-background)"
|
|
355
|
+
strokeWidth={2}
|
|
356
|
+
/>
|
|
357
|
+
</svg>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function AuthorAvatar({
|
|
362
|
+
author,
|
|
363
|
+
className,
|
|
364
|
+
fallbackClassName,
|
|
365
|
+
}: {
|
|
366
|
+
author: CommitAuthor;
|
|
367
|
+
className?: string;
|
|
368
|
+
fallbackClassName?: string;
|
|
369
|
+
}) {
|
|
370
|
+
return (
|
|
371
|
+
<Avatar className={cn("size-4", className)}>
|
|
372
|
+
{author.avatarUrl ? <AvatarImage src={author.avatarUrl} alt="" /> : null}
|
|
373
|
+
<AvatarFallback className={cn("text-[8px] font-bold", fallbackClassName)}>
|
|
374
|
+
{initials(author.name)}
|
|
375
|
+
</AvatarFallback>
|
|
376
|
+
</Avatar>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function formatDate(
|
|
381
|
+
date: string | Date,
|
|
382
|
+
t: UiMessages["commitGraph"],
|
|
383
|
+
locale: string,
|
|
384
|
+
): string {
|
|
385
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
386
|
+
const now = new Date();
|
|
387
|
+
const diffMs = now.getTime() - d.getTime();
|
|
388
|
+
const diffMins = Math.floor(diffMs / 60_000);
|
|
389
|
+
const diffHours = Math.floor(diffMs / 3_600_000);
|
|
390
|
+
const diffDays = Math.floor(diffMs / 86_400_000);
|
|
391
|
+
|
|
392
|
+
if (diffMins < 1) return t.justNow;
|
|
393
|
+
if (diffMins < 60) return t.minutesAgo(diffMins);
|
|
394
|
+
if (diffHours < 24) return t.hoursAgo(diffHours);
|
|
395
|
+
if (diffDays < 7) return t.daysAgo(diffDays);
|
|
396
|
+
|
|
397
|
+
return d.toLocaleDateString(locale, {
|
|
398
|
+
month: "short",
|
|
399
|
+
day: "numeric",
|
|
400
|
+
year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function formatFullDate(date: string | Date, locale: string): string {
|
|
405
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
406
|
+
return d.toLocaleDateString(locale, {
|
|
407
|
+
weekday: "short",
|
|
408
|
+
month: "short",
|
|
409
|
+
day: "numeric",
|
|
410
|
+
year: "numeric",
|
|
411
|
+
hour: "numeric",
|
|
412
|
+
minute: "2-digit",
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function CommitDetail({
|
|
417
|
+
commit,
|
|
418
|
+
hashLength,
|
|
419
|
+
railColor,
|
|
420
|
+
children,
|
|
421
|
+
className,
|
|
422
|
+
style,
|
|
423
|
+
}: {
|
|
424
|
+
commit: Commit;
|
|
425
|
+
hashLength: number;
|
|
426
|
+
railColor: string;
|
|
427
|
+
children: ReactNode;
|
|
428
|
+
className?: string;
|
|
429
|
+
style?: CSSProperties;
|
|
430
|
+
}) {
|
|
431
|
+
const t = useLabels("commitGraph");
|
|
432
|
+
const dateLocale = useDateLocale();
|
|
433
|
+
const locale = dateLocale.code ?? "en-US";
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<Popover>
|
|
437
|
+
<PopoverTrigger
|
|
438
|
+
type="button"
|
|
439
|
+
data-slot="commit-entry"
|
|
440
|
+
className={className}
|
|
441
|
+
style={style}
|
|
442
|
+
>
|
|
443
|
+
{children}
|
|
444
|
+
</PopoverTrigger>
|
|
445
|
+
<PopoverContent side="right" sideOffset={8} className="w-80 p-3">
|
|
446
|
+
<div className="flex flex-col gap-2">
|
|
447
|
+
<p className="text-sm leading-snug font-medium">{commit.message}</p>
|
|
448
|
+
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
449
|
+
<span className="inline-flex items-center gap-1.5">
|
|
450
|
+
<AuthorAvatar
|
|
451
|
+
author={commit.author}
|
|
452
|
+
className="size-3.5"
|
|
453
|
+
fallbackClassName="text-[7px]"
|
|
454
|
+
/>
|
|
455
|
+
{commit.author.name}
|
|
456
|
+
</span>
|
|
457
|
+
<span className="text-border">·</span>
|
|
458
|
+
<code className="rounded bg-muted px-1 py-0.5 font-mono text-[10px]">
|
|
459
|
+
{commit.hash.slice(0, hashLength)}
|
|
460
|
+
</code>
|
|
461
|
+
</div>
|
|
462
|
+
<div className="text-[11px] text-muted-foreground">
|
|
463
|
+
{formatFullDate(commit.date, locale)}
|
|
464
|
+
</div>
|
|
465
|
+
{(commit.refs || commit.tag) && (
|
|
466
|
+
<div className="flex flex-wrap gap-1">
|
|
467
|
+
{commit.refs?.map((ref) => (
|
|
468
|
+
<span
|
|
469
|
+
key={ref}
|
|
470
|
+
className="inline-flex items-center gap-1 rounded-md border border-border/60 bg-muted/50 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground"
|
|
471
|
+
>
|
|
472
|
+
{ref}
|
|
473
|
+
</span>
|
|
474
|
+
))}
|
|
475
|
+
{commit.tag && (
|
|
476
|
+
<span
|
|
477
|
+
className="inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium"
|
|
478
|
+
style={{
|
|
479
|
+
backgroundColor: `${railColor}20`,
|
|
480
|
+
color: railColor,
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
{commit.tag}
|
|
484
|
+
</span>
|
|
485
|
+
)}
|
|
486
|
+
</div>
|
|
487
|
+
)}
|
|
488
|
+
{commit.parents.length > 0 && (
|
|
489
|
+
<div className="text-[10px] text-muted-foreground/60">
|
|
490
|
+
{commit.parents.length === 1 ? t.parent : t.parents}:{" "}
|
|
491
|
+
{commit.parents.map((p) => p.slice(0, hashLength)).join(", ")}
|
|
492
|
+
</div>
|
|
493
|
+
)}
|
|
494
|
+
</div>
|
|
495
|
+
</PopoverContent>
|
|
496
|
+
</Popover>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Topological git commit graph (forks / merges).
|
|
502
|
+
* @when App history, release notes, git timeline views
|
|
503
|
+
* @example
|
|
504
|
+
* <CommitGraph commits={[{ hash, message, author, date, parents }]} />
|
|
505
|
+
* @family Chart & data
|
|
506
|
+
*/
|
|
507
|
+
export function CommitGraph({
|
|
508
|
+
commits,
|
|
509
|
+
truncateHash = 7,
|
|
510
|
+
railWidth = 24,
|
|
511
|
+
className,
|
|
512
|
+
...props
|
|
513
|
+
}: CommitGraphProps) {
|
|
514
|
+
const t = useLabels("commitGraph");
|
|
515
|
+
const dateLocale = useDateLocale();
|
|
516
|
+
const locale = dateLocale.code ?? "en-US";
|
|
517
|
+
|
|
518
|
+
// Simple mode: if no commit has parents, infer a linear topology
|
|
519
|
+
const hasTopology = commits.some((c) => c.parents && c.parents.length > 0);
|
|
520
|
+
const resolvedCommits = hasTopology
|
|
521
|
+
? commits
|
|
522
|
+
: commits.map((c, i) => {
|
|
523
|
+
const next = commits[i + 1];
|
|
524
|
+
return {
|
|
525
|
+
...c,
|
|
526
|
+
parents: next ? [next.hash] : [],
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
if (resolvedCommits.length === 0) {
|
|
531
|
+
return (
|
|
532
|
+
<div
|
|
533
|
+
data-slot="commit-graph"
|
|
534
|
+
data-testid="commit-graph"
|
|
535
|
+
className={cn(
|
|
536
|
+
"flex items-center justify-center rounded-xl border border-border/60 bg-card py-10 text-sm text-muted-foreground shadow-sm",
|
|
537
|
+
className,
|
|
538
|
+
)}
|
|
539
|
+
{...props}
|
|
540
|
+
>
|
|
541
|
+
{t.empty}
|
|
542
|
+
</div>
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const rows = computeLayout(resolvedCommits);
|
|
547
|
+
const maxRails = Math.max(
|
|
548
|
+
...rows.map((r) =>
|
|
549
|
+
Math.max(
|
|
550
|
+
r.rail + 1,
|
|
551
|
+
r.rails.length,
|
|
552
|
+
...r.edges.map((e) => Math.max(e.fromRail, e.toRail) + 1),
|
|
553
|
+
),
|
|
554
|
+
),
|
|
555
|
+
);
|
|
556
|
+
const svgWidth = maxRails * railWidth;
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<div
|
|
560
|
+
data-slot="commit-graph"
|
|
561
|
+
data-testid="commit-graph"
|
|
562
|
+
className={cn(
|
|
563
|
+
"overflow-hidden rounded-xl border border-border/60 bg-card shadow-sm",
|
|
564
|
+
className,
|
|
565
|
+
)}
|
|
566
|
+
{...props}
|
|
567
|
+
>
|
|
568
|
+
<div className="overflow-x-auto">
|
|
569
|
+
{rows.map((row, i) => {
|
|
570
|
+
const prevRow = i > 0 ? (rows[i - 1] ?? null) : null;
|
|
571
|
+
return (
|
|
572
|
+
<CommitDetail
|
|
573
|
+
key={row.commit.hash}
|
|
574
|
+
commit={row.commit}
|
|
575
|
+
hashLength={truncateHash}
|
|
576
|
+
railColor={color(row.rail)}
|
|
577
|
+
className="flex w-full items-center gap-0 border-b border-border/30 transition-colors last:border-b-0 hover:bg-muted/30 focus-visible:bg-muted/30 focus-visible:outline-none"
|
|
578
|
+
style={{ height: ROW_HEIGHT }}
|
|
579
|
+
>
|
|
580
|
+
{/* Rails */}
|
|
581
|
+
<div style={{ width: svgWidth }} className="shrink-0">
|
|
582
|
+
<RailsSVG
|
|
583
|
+
row={row}
|
|
584
|
+
prevRow={prevRow}
|
|
585
|
+
railWidth={railWidth}
|
|
586
|
+
maxRails={maxRails}
|
|
587
|
+
/>
|
|
588
|
+
</div>
|
|
589
|
+
|
|
590
|
+
{/* Refs */}
|
|
591
|
+
<div className="flex shrink-0 items-center gap-1 px-2">
|
|
592
|
+
{row.commit.refs?.map((ref) => (
|
|
593
|
+
<span
|
|
594
|
+
key={ref}
|
|
595
|
+
className="inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-[10px] leading-none font-semibold"
|
|
596
|
+
style={{
|
|
597
|
+
borderColor: `${color(row.rail)}40`,
|
|
598
|
+
backgroundColor: `${color(row.rail)}10`,
|
|
599
|
+
color: color(row.rail),
|
|
600
|
+
}}
|
|
601
|
+
>
|
|
602
|
+
{ref}
|
|
603
|
+
</span>
|
|
604
|
+
))}
|
|
605
|
+
{row.commit.tag && (
|
|
606
|
+
<span
|
|
607
|
+
className="inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] leading-none font-semibold"
|
|
608
|
+
style={{
|
|
609
|
+
backgroundColor: `${color(row.rail)}20`,
|
|
610
|
+
color: color(row.rail),
|
|
611
|
+
}}
|
|
612
|
+
>
|
|
613
|
+
{row.commit.tag}
|
|
614
|
+
</span>
|
|
615
|
+
)}
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
{/* Message */}
|
|
619
|
+
<p className="min-w-0 flex-1 truncate px-2 text-left text-sm text-foreground/80">
|
|
620
|
+
{row.commit.message}
|
|
621
|
+
</p>
|
|
622
|
+
|
|
623
|
+
{/* Meta */}
|
|
624
|
+
<div className="flex shrink-0 items-center gap-3 px-3">
|
|
625
|
+
<code className="font-mono text-[11px] text-muted-foreground/60">
|
|
626
|
+
{row.commit.hash.slice(0, truncateHash)}
|
|
627
|
+
</code>
|
|
628
|
+
<span className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
629
|
+
<AuthorAvatar author={row.commit.author} />
|
|
630
|
+
<span className="hidden sm:inline">
|
|
631
|
+
{row.commit.author.name}
|
|
632
|
+
</span>
|
|
633
|
+
</span>
|
|
634
|
+
<span className="text-[11px] text-muted-foreground/50">
|
|
635
|
+
{formatDate(row.commit.date, t, locale)}
|
|
636
|
+
</span>
|
|
637
|
+
</div>
|
|
638
|
+
</CommitDetail>
|
|
639
|
+
);
|
|
640
|
+
})}
|
|
641
|
+
</div>
|
|
642
|
+
</div>
|
|
643
|
+
);
|
|
644
|
+
}
|
|
@@ -352,7 +352,8 @@ function GridToolbar<TData>({
|
|
|
352
352
|
* pageIndex={page}
|
|
353
353
|
* onPageIndexChange={setPage}
|
|
354
354
|
* />
|
|
355
|
-
* //
|
|
355
|
+
* // “back to page 1” button: setPage(0); for a structural reload you can also remount via key
|
|
356
|
+
* @family Data & tables
|
|
356
357
|
*/
|
|
357
358
|
export function DataGrid<TData>({
|
|
358
359
|
columns,
|