@monkey-mini-app/ui 0.1.1 → 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 +12 -6
- 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 +11 -0
- 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 +7 -0
- 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 +7 -0
- 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 +7 -0
- package/dist/src/blocks/radar-chart.tsx +7 -0
- 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 +7 -0
- 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 +1 -0
- package/dist/vendors/lodash.js +20 -0
- package/dist/vendors/motion.js +12 -0
- package/package.json +20 -19
- package/dist/src/products/jql-language.ts +0 -70
|
@@ -1,89 +1,238 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Check } from "lucide-react";
|
|
4
5
|
|
|
5
|
-
import { cn } from "@monkey-mini-app/ui/lib/utils"
|
|
6
|
+
import { cn } from "@monkey-mini-app/ui/lib/utils";
|
|
6
7
|
|
|
7
|
-
export type
|
|
8
|
+
export type StepStatus = "default" | "active" | "completed";
|
|
8
9
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
export interface StepperProps {
|
|
11
|
+
/** StepperItem elements. */
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/** Layout direction. Defaults to "vertical". */
|
|
14
|
+
orientation?: "vertical" | "horizontal";
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StepperItemProps {
|
|
19
|
+
/** Step heading text. */
|
|
20
|
+
title: string;
|
|
21
|
+
/** Optional description below the title. */
|
|
22
|
+
description?: string;
|
|
23
|
+
/** Override the auto-incremented step number. */
|
|
24
|
+
step?: number;
|
|
25
|
+
/** Visual status of this step. Defaults to "default". */
|
|
26
|
+
status?: StepStatus;
|
|
27
|
+
/** Custom icon to replace the step number circle. */
|
|
28
|
+
icon?: React.ReactNode;
|
|
29
|
+
/** Content rendered below the step header (code blocks, text, etc.). */
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One step inside `Stepper` (title, description, status).
|
|
36
|
+
* @family Feedback & status
|
|
37
|
+
* @when As a child of `Stepper` only — status is `default | active | completed` (lowercase).
|
|
38
|
+
*/
|
|
39
|
+
export function StepperItem({
|
|
40
|
+
title,
|
|
41
|
+
description,
|
|
42
|
+
step,
|
|
43
|
+
status = "default",
|
|
44
|
+
icon,
|
|
45
|
+
children,
|
|
46
|
+
className,
|
|
47
|
+
}: StepperItemProps) {
|
|
48
|
+
const stepNumber = step ?? 1;
|
|
21
49
|
|
|
22
50
|
return (
|
|
23
|
-
<
|
|
24
|
-
data-
|
|
25
|
-
data-
|
|
26
|
-
className={cn(
|
|
51
|
+
<div
|
|
52
|
+
data-slot="stepper-item"
|
|
53
|
+
data-status={status}
|
|
54
|
+
className={cn("group/step relative", className)}
|
|
27
55
|
>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const clickable = Boolean(onStepClick)
|
|
32
|
-
return (
|
|
33
|
-
<li
|
|
34
|
-
key={step.id}
|
|
56
|
+
<div className="flex gap-3">
|
|
57
|
+
<div className="flex shrink-0 flex-col items-center self-stretch">
|
|
58
|
+
<div
|
|
35
59
|
className={cn(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
"relative z-10 flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-sm font-semibold transition-colors",
|
|
61
|
+
status === "completed" &&
|
|
62
|
+
"border-primary bg-primary text-primary-foreground",
|
|
63
|
+
status === "active" &&
|
|
64
|
+
"border-primary bg-primary/10 text-primary",
|
|
65
|
+
status === "default" &&
|
|
66
|
+
"border-border bg-background text-muted-foreground"
|
|
39
67
|
)}
|
|
40
68
|
>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
{icon ? (
|
|
70
|
+
icon
|
|
71
|
+
) : status === "completed" ? (
|
|
72
|
+
<Check className="size-3.5" aria-hidden="true" />
|
|
73
|
+
) : (
|
|
74
|
+
<span className="text-xs">{stepNumber}</span>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div
|
|
79
|
+
className={cn(
|
|
80
|
+
"w-px min-h-4 grow",
|
|
81
|
+
status === "completed"
|
|
82
|
+
? "bg-primary"
|
|
83
|
+
: "bg-border dark:bg-muted-foreground/25",
|
|
84
|
+
"group-last/step:hidden"
|
|
85
|
+
)}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="flex min-w-0 flex-col gap-1 pb-6 pt-1 group-last/step:pb-0">
|
|
90
|
+
<div className="flex flex-col gap-0.5">
|
|
91
|
+
<h3 className="text-sm font-semibold leading-tight text-foreground">
|
|
92
|
+
{title}
|
|
93
|
+
</h3>
|
|
94
|
+
{description ? (
|
|
95
|
+
<p className="text-sm text-muted-foreground">{description}</p>
|
|
96
|
+
) : null}
|
|
97
|
+
</div>
|
|
98
|
+
{children ? <div className="mt-2">{children}</div> : null}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function VerticalStepper({
|
|
106
|
+
children,
|
|
107
|
+
className,
|
|
108
|
+
}: {
|
|
109
|
+
children: React.ReactNode;
|
|
110
|
+
className?: string;
|
|
111
|
+
}) {
|
|
112
|
+
const items = React.Children.toArray(children).filter(React.isValidElement);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div
|
|
116
|
+
data-testid="stepper"
|
|
117
|
+
data-slot="stepper"
|
|
118
|
+
data-orientation="vertical"
|
|
119
|
+
className={cn("flex flex-col", className)}
|
|
120
|
+
>
|
|
121
|
+
{items.map((child, index) => {
|
|
122
|
+
if (!React.isValidElement<StepperItemProps>(child)) return child;
|
|
123
|
+
return React.cloneElement(child, {
|
|
124
|
+
step: child.props.step ?? index + 1,
|
|
125
|
+
key: child.key ?? index,
|
|
126
|
+
});
|
|
127
|
+
})}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function HorizontalStepper({
|
|
133
|
+
children,
|
|
134
|
+
className,
|
|
135
|
+
}: {
|
|
136
|
+
children: React.ReactNode;
|
|
137
|
+
className?: string;
|
|
138
|
+
}) {
|
|
139
|
+
const items = React.Children.toArray(children).filter(React.isValidElement);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div
|
|
143
|
+
data-testid="stepper"
|
|
144
|
+
data-slot="stepper"
|
|
145
|
+
data-orientation="horizontal"
|
|
146
|
+
className={cn("flex flex-col gap-4", className)}
|
|
147
|
+
>
|
|
148
|
+
<div className="flex items-center">
|
|
149
|
+
{items.map((child, index) => {
|
|
150
|
+
if (!React.isValidElement<StepperItemProps>(child)) return null;
|
|
151
|
+
const { status = "default", icon, step: stepProp } = child.props;
|
|
152
|
+
const stepNumber = stepProp ?? index + 1;
|
|
153
|
+
const isLast = index === items.length - 1;
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<React.Fragment key={child.key ?? index}>
|
|
157
|
+
<div className="flex flex-col items-center gap-1.5">
|
|
158
|
+
<div
|
|
49
159
|
className={cn(
|
|
50
|
-
"
|
|
51
|
-
|
|
160
|
+
"flex size-8 shrink-0 items-center justify-center rounded-full border-2 text-sm font-semibold transition-colors",
|
|
161
|
+
status === "completed" &&
|
|
162
|
+
"border-primary bg-primary text-primary-foreground",
|
|
163
|
+
status === "active" &&
|
|
164
|
+
"border-primary bg-primary/10 text-primary",
|
|
165
|
+
status === "default" &&
|
|
166
|
+
"border-border bg-background text-muted-foreground"
|
|
52
167
|
)}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
state === "current" && "border-2 border-primary text-primary",
|
|
63
|
-
state === "todo" && "border text-muted-foreground",
|
|
64
|
-
clickable && "cursor-pointer"
|
|
65
|
-
)}
|
|
66
|
-
>
|
|
67
|
-
{state === "done" ? <Check className="size-3.5" /> : index + 1}
|
|
68
|
-
</button>
|
|
69
|
-
{horizontal && index < steps.length - 1 ? (
|
|
168
|
+
>
|
|
169
|
+
{icon ? (
|
|
170
|
+
icon
|
|
171
|
+
) : status === "completed" ? (
|
|
172
|
+
<Check className="size-3.5" aria-hidden="true" />
|
|
173
|
+
) : (
|
|
174
|
+
<span className="text-xs">{stepNumber}</span>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
70
177
|
<span
|
|
71
178
|
className={cn(
|
|
72
|
-
"
|
|
73
|
-
|
|
179
|
+
"max-w-[8rem] text-center text-xs font-medium",
|
|
180
|
+
status === "default"
|
|
181
|
+
? "text-muted-foreground"
|
|
182
|
+
: "text-foreground"
|
|
183
|
+
)}
|
|
184
|
+
>
|
|
185
|
+
{child.props.title}
|
|
186
|
+
</span>
|
|
187
|
+
</div>
|
|
188
|
+
{!isLast ? (
|
|
189
|
+
<div
|
|
190
|
+
className={cn(
|
|
191
|
+
"mb-5 h-px flex-1",
|
|
192
|
+
status === "completed" ? "bg-primary" : "bg-border"
|
|
74
193
|
)}
|
|
75
194
|
/>
|
|
76
195
|
) : null}
|
|
77
|
-
</
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
196
|
+
</React.Fragment>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{items.map((child, index) => {
|
|
202
|
+
if (!React.isValidElement<StepperItemProps>(child)) return null;
|
|
203
|
+
const { status = "default", description, children: stepChildren } =
|
|
204
|
+
child.props;
|
|
205
|
+
if (status !== "active") return null;
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div key={child.key ?? index} className="flex flex-col gap-1.5">
|
|
209
|
+
{description ? (
|
|
210
|
+
<p className="text-sm text-muted-foreground">{description}</p>
|
|
211
|
+
) : null}
|
|
212
|
+
{stepChildren ? <div>{stepChildren}</div> : null}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
86
215
|
})}
|
|
87
|
-
</
|
|
88
|
-
)
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Numbered steps with status; children are `StepperItem`.
|
|
222
|
+
* @family Feedback & status
|
|
223
|
+
* @when Wizard/progress of an ordered process. Time axis → `RunTimeline`.
|
|
224
|
+
* @example
|
|
225
|
+
* <Stepper><StepperItem title="上传" status="pass" /><StepperItem title="校验" status="running" /></Stepper>
|
|
226
|
+
*/
|
|
227
|
+
export function Stepper({
|
|
228
|
+
children,
|
|
229
|
+
orientation = "vertical",
|
|
230
|
+
className,
|
|
231
|
+
}: StepperProps) {
|
|
232
|
+
if (orientation === "horizontal") {
|
|
233
|
+
return (
|
|
234
|
+
<HorizontalStepper className={className}>{children}</HorizontalStepper>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return <VerticalStepper className={className}>{children}</VerticalStepper>;
|
|
89
238
|
}
|
|
@@ -7,6 +7,13 @@ export type TimelineItem = {
|
|
|
7
7
|
time?: string
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Vertical ordered events.
|
|
12
|
+
* @when Statuses/history without durations. Durations → `RunTimeline`.
|
|
13
|
+
* @example
|
|
14
|
+
* <Timeline items={[{ id: "1", title: "已创建", time: "09:00" }]} />
|
|
15
|
+
* @family Realtime
|
|
16
|
+
*/
|
|
10
17
|
export function Timeline({ items }: { items: TimelineItem[] }) {
|
|
11
18
|
return (
|
|
12
19
|
<ol className="relative space-y-4 border-l pl-4" data-testid="timeline">
|
|
@@ -45,6 +45,13 @@ function Node({
|
|
|
45
45
|
)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Generic expandable node list.
|
|
50
|
+
* @when Any hierarchy without file semantics. Files → `FileTree`.
|
|
51
|
+
* @example
|
|
52
|
+
* <TreeView nodes={[{ id: "a", label: "华东" }]} />
|
|
53
|
+
* @family Data & tables
|
|
54
|
+
*/
|
|
48
55
|
export function TreeView({ nodes }: { nodes: TreeNode[] }) {
|
|
49
56
|
return (
|
|
50
57
|
<div data-testid="tree-view">
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { useLabels } from "../i18n/context";
|
|
4
|
+
import { AppIdContext } from "./app-id";
|
|
5
|
+
|
|
6
|
+
/** Where a captured failure came from — drives the hint the human sees. */
|
|
7
|
+
export type AppErrorKind = "render" | "module" | "uncaught" | "async";
|
|
8
|
+
|
|
9
|
+
export type ReportedAppError = {
|
|
10
|
+
kind: AppErrorKind;
|
|
11
|
+
message: string;
|
|
12
|
+
file?: string;
|
|
13
|
+
line?: number;
|
|
14
|
+
column?: number;
|
|
15
|
+
stack?: string;
|
|
16
|
+
componentStack?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hand an error to the host so `mini_app_errors` can see it.
|
|
21
|
+
*
|
|
22
|
+
* The app iframe is same-origin with the host, but the panel embedding it is not — so the
|
|
23
|
+
* only channel that works in both directions is the iframe POSTing to its own host. This
|
|
24
|
+
* is deliberately fire-and-forget: a diagnostic path must never be able to break the app
|
|
25
|
+
* that is reporting through it.
|
|
26
|
+
*/
|
|
27
|
+
export function reportAppError(appId: string, err: ReportedAppError): void {
|
|
28
|
+
if (!appId) return;
|
|
29
|
+
try {
|
|
30
|
+
const body = JSON.stringify(err);
|
|
31
|
+
const url = `/api/app/${encodeURIComponent(appId)}/errors`;
|
|
32
|
+
// sendBeacon survives the unmount that a crashing app often triggers right after.
|
|
33
|
+
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
34
|
+
const sent = navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
35
|
+
if (sent) return;
|
|
36
|
+
}
|
|
37
|
+
void fetch(url, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "content-type": "application/json" },
|
|
40
|
+
body,
|
|
41
|
+
keepalive: true,
|
|
42
|
+
}).catch(() => {});
|
|
43
|
+
} catch {
|
|
44
|
+
/* never throw from a failure handler */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const HINT_KEY: Record<AppErrorKind, "renderHint" | "moduleHint" | "uncaughtHint" | "asyncHint"> = {
|
|
49
|
+
render: "renderHint",
|
|
50
|
+
module: "moduleHint",
|
|
51
|
+
uncaught: "uncaughtHint",
|
|
52
|
+
async: "asyncHint",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function ErrorCard(props: {
|
|
56
|
+
appId: string;
|
|
57
|
+
kind: AppErrorKind;
|
|
58
|
+
message: string;
|
|
59
|
+
stack?: string;
|
|
60
|
+
componentStack?: string;
|
|
61
|
+
onRetry: () => void;
|
|
62
|
+
}) {
|
|
63
|
+
const t = useLabels("appError");
|
|
64
|
+
const [open, setOpen] = React.useState(false);
|
|
65
|
+
const detail = [props.componentStack, props.stack].filter(Boolean).join("\n");
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
role="alert"
|
|
70
|
+
style={{
|
|
71
|
+
boxSizing: "border-box",
|
|
72
|
+
margin: "24px",
|
|
73
|
+
padding: "16px 18px",
|
|
74
|
+
borderRadius: 12,
|
|
75
|
+
border: "1px solid var(--destructive,#dc2626)",
|
|
76
|
+
background: "var(--card,#fff)",
|
|
77
|
+
color: "var(--foreground,#111)",
|
|
78
|
+
font: "13px/1.6 var(--font-sans,ui-sans-serif,system-ui,sans-serif)",
|
|
79
|
+
maxWidth: 720,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
|
|
83
|
+
<span
|
|
84
|
+
style={{
|
|
85
|
+
width: 8,
|
|
86
|
+
height: 8,
|
|
87
|
+
borderRadius: "50%",
|
|
88
|
+
background: "var(--destructive,#dc2626)",
|
|
89
|
+
flex: "0 0 auto",
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
<strong style={{ fontSize: 14 }}>{t.title}</strong>
|
|
93
|
+
<code
|
|
94
|
+
style={{
|
|
95
|
+
marginLeft: "auto",
|
|
96
|
+
padding: "1px 6px",
|
|
97
|
+
borderRadius: 6,
|
|
98
|
+
background: "var(--muted,#f3f4f6)",
|
|
99
|
+
color: "var(--muted-foreground,#6b7280)",
|
|
100
|
+
fontSize: 11,
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
{props.appId}
|
|
104
|
+
</code>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<p style={{ margin: "0 0 8px", color: "var(--muted-foreground,#6b7280)" }}>{t[HINT_KEY[props.kind]]}</p>
|
|
108
|
+
|
|
109
|
+
<pre
|
|
110
|
+
style={{
|
|
111
|
+
margin: 0,
|
|
112
|
+
padding: "10px 12px",
|
|
113
|
+
borderRadius: 8,
|
|
114
|
+
background: "var(--muted,#f3f4f6)",
|
|
115
|
+
whiteSpace: "pre-wrap",
|
|
116
|
+
wordBreak: "break-word",
|
|
117
|
+
fontFamily: "var(--font-mono,ui-monospace,SFMono-Regular,monospace)",
|
|
118
|
+
fontSize: 12,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{props.message}
|
|
122
|
+
</pre>
|
|
123
|
+
|
|
124
|
+
<div style={{ display: "flex", gap: 8, alignItems: "center", marginTop: 12 }}>
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
onClick={props.onRetry}
|
|
128
|
+
style={{
|
|
129
|
+
padding: "5px 12px",
|
|
130
|
+
borderRadius: 8,
|
|
131
|
+
border: "1px solid var(--border,#e5e7eb)",
|
|
132
|
+
background: "var(--primary,#2563eb)",
|
|
133
|
+
color: "var(--primary-foreground,#fff)",
|
|
134
|
+
font: "inherit",
|
|
135
|
+
cursor: "pointer",
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
{t.retry}
|
|
139
|
+
</button>
|
|
140
|
+
{detail ? (
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onClick={() => setOpen((v) => !v)}
|
|
144
|
+
style={{
|
|
145
|
+
padding: "5px 12px",
|
|
146
|
+
borderRadius: 8,
|
|
147
|
+
border: "1px solid var(--border,#e5e7eb)",
|
|
148
|
+
background: "transparent",
|
|
149
|
+
color: "var(--foreground,#111)",
|
|
150
|
+
font: "inherit",
|
|
151
|
+
cursor: "pointer",
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
{open ? t.hideDetails : t.details}
|
|
155
|
+
</button>
|
|
156
|
+
) : null}
|
|
157
|
+
<span style={{ marginLeft: "auto", fontSize: 11, color: "var(--muted-foreground,#6b7280)" }}>
|
|
158
|
+
{t.reported}
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{open && detail ? (
|
|
163
|
+
<pre
|
|
164
|
+
style={{
|
|
165
|
+
margin: "10px 0 0",
|
|
166
|
+
padding: "10px 12px",
|
|
167
|
+
borderRadius: 8,
|
|
168
|
+
background: "var(--muted,#f3f4f6)",
|
|
169
|
+
color: "var(--muted-foreground,#6b7280)",
|
|
170
|
+
whiteSpace: "pre-wrap",
|
|
171
|
+
wordBreak: "break-word",
|
|
172
|
+
maxHeight: 260,
|
|
173
|
+
overflow: "auto",
|
|
174
|
+
fontFamily: "var(--font-mono,ui-monospace,SFMono-Regular,monospace)",
|
|
175
|
+
fontSize: 11,
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
{detail}
|
|
179
|
+
</pre>
|
|
180
|
+
) : null}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
type BoundaryProps = { children: React.ReactNode };
|
|
186
|
+
/** Never `null`: React types `state` as `Readonly<S>`, and a nullable S is not a valid JSX element type. */
|
|
187
|
+
type BoundaryState = { error?: Error; componentStack?: string };
|
|
188
|
+
|
|
189
|
+
class AppErrorBoundaryClass extends React.Component<
|
|
190
|
+
BoundaryProps & { appId: string },
|
|
191
|
+
BoundaryState
|
|
192
|
+
> {
|
|
193
|
+
/** Required: without an initializer `this.state` is null and the first render throws. */
|
|
194
|
+
state: BoundaryState = {};
|
|
195
|
+
|
|
196
|
+
static getDerivedStateFromError(error: Error): BoundaryState {
|
|
197
|
+
return { error };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
componentDidCatch(error: Error, info: { componentStack?: string }): void {
|
|
201
|
+
const componentStack = typeof info?.componentStack === "string" ? info.componentStack : undefined;
|
|
202
|
+
this.setState({ componentStack });
|
|
203
|
+
reportAppError(this.props.appId, {
|
|
204
|
+
kind: "render",
|
|
205
|
+
message: error?.message || String(error),
|
|
206
|
+
stack: error?.stack,
|
|
207
|
+
componentStack,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
render(): React.ReactNode {
|
|
212
|
+
const error = this.state.error;
|
|
213
|
+
if (error) {
|
|
214
|
+
return (
|
|
215
|
+
<ErrorCard
|
|
216
|
+
appId={this.props.appId}
|
|
217
|
+
kind="render"
|
|
218
|
+
message={error.message || String(error)}
|
|
219
|
+
stack={error.stack}
|
|
220
|
+
componentStack={this.state.componentStack}
|
|
221
|
+
onRetry={() => this.setState({ error: undefined, componentStack: undefined })}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return this.props.children;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Runtime crash guard for a mini-app UI. The host injects this around the app entry when
|
|
231
|
+
* it compiles `ui.tsx`, so authors never have to add it — and an app that throws during
|
|
232
|
+
* render shows a real message instead of a blank iframe.
|
|
233
|
+
*
|
|
234
|
+
* @when Not used from mini-app code: it is already wrapped around your `ui.tsx` by the host.
|
|
235
|
+
* @family Feedback & status
|
|
236
|
+
*/
|
|
237
|
+
export function AppErrorBoundary(props: { children: React.ReactNode }) {
|
|
238
|
+
const appId = React.useContext(AppIdContext);
|
|
239
|
+
return <AppErrorBoundaryClass appId={appId} key={appId} {...props} />;
|
|
240
|
+
}
|