@arronqzy/react-blueprint 1.0.6 → 1.0.8
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/package.json +3 -3
- package/src/BlueprintContextMenu.tsx +1 -1
- package/src/BlueprintNodeConfigSidebar.tsx +115 -28
- package/src/blueprintNodeTypes.ts +2 -0
- package/src/components/ClockNodeConfigPanel.tsx +17 -24
- package/src/components/ConfigHintIcon.tsx +83 -0
- package/src/components/FetchNodeConfigPanel.tsx +204 -32
- package/src/components/FetchUrlAutocomplete.tsx +1 -0
- package/src/components/JsonNodeConfigPanel.tsx +5 -6
- package/src/components/LogicNodeConfigPanel.tsx +5 -6
- package/src/components/ScopeTemplateAutocompleteHost.tsx +245 -0
- package/src/graph/blueprint-graph.ts +8 -0
- package/src/graph/document.ts +136 -8
- package/src/graph/node-summary.ts +20 -0
- package/src/graph/node-types.ts +1 -0
- package/src/graph/react-flow-adapter.ts +4 -0
- package/src/graph/sync-nodes.ts +2 -1
- package/src/hooks/useBlueprintFlowState.ts +40 -7
- package/src/hooks/useBlueprintPageLifecycle.ts +106 -14
- package/src/index.tsx +2 -0
- package/src/nodes/BlueprintNodeCard.tsx +12 -2
- package/src/nodes/BlueprintNodeShell.tsx +6 -2
- package/src/nodes/EventFlowNode.tsx +31 -0
- package/src/runtime/document-to-runnable-graph.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/react-blueprint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"dayjs": "^1.11.13",
|
|
13
13
|
"react-dom": "^19",
|
|
14
14
|
"@xyflow/react": "^12.9.3",
|
|
15
|
-
"@arronqzy/blueprint-dsl": "1.0.
|
|
16
|
-
"@arronqzy/i18n": "0.1.3",
|
|
15
|
+
"@arronqzy/blueprint-dsl": "1.0.7",
|
|
17
16
|
"@arronqzy/react-rx-store": "1.1.4",
|
|
17
|
+
"@arronqzy/i18n": "0.1.6",
|
|
18
18
|
"@arronqzy/ui": "1.0.5"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
@@ -12,7 +12,7 @@ export type BlueprintContextMenuState =
|
|
|
12
12
|
clientX: number;
|
|
13
13
|
clientY: number;
|
|
14
14
|
nodeId: string;
|
|
15
|
-
role: "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock";
|
|
15
|
+
role: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "clock";
|
|
16
16
|
}
|
|
17
17
|
| {
|
|
18
18
|
kind: "edge";
|
|
@@ -6,11 +6,15 @@ import {
|
|
|
6
6
|
SelectItem,
|
|
7
7
|
SelectTrigger,
|
|
8
8
|
SelectValue,
|
|
9
|
+
Checkbox,
|
|
9
10
|
} from "@arronqzy/ui";
|
|
10
11
|
import {
|
|
11
12
|
LIFECYCLE_NODE_TYPE,
|
|
12
13
|
PAGE_LIFECYCLE_PHASES,
|
|
14
|
+
VIEW_EVENT_TYPES,
|
|
15
|
+
type ExecutionTraceEntry,
|
|
13
16
|
type PageLifecyclePhase,
|
|
17
|
+
type ViewEventType,
|
|
14
18
|
} from "@arronqzy/blueprint-dsl";
|
|
15
19
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
16
20
|
|
|
@@ -19,13 +23,18 @@ import { ClockNodeConfigPanel } from "./components/ClockNodeConfigPanel";
|
|
|
19
23
|
import { JsonNodeConfigPanel } from "./components/JsonNodeConfigPanel";
|
|
20
24
|
import { LogicNodeConfigPanel } from "./components/LogicNodeConfigPanel";
|
|
21
25
|
import { ViewElementMultiSelect } from "./components/ViewElementMultiSelect";
|
|
26
|
+
import { ConfigFieldLabel, ConfigHintIcon, ConfigSectionTitle } from "./components/ConfigHintIcon";
|
|
22
27
|
import {
|
|
23
28
|
getLifecyclePhaseLabel,
|
|
29
|
+
getViewEventTypeLabel,
|
|
30
|
+
inspectEventNodeInputs,
|
|
24
31
|
patchNodeConfigSource,
|
|
25
32
|
pruneViewElementIds,
|
|
26
33
|
resolveBlueprintConfigSource,
|
|
34
|
+
resolveNodeEventConfig,
|
|
27
35
|
resolveViewElementIds,
|
|
28
36
|
type BlueprintConfigSource,
|
|
37
|
+
type BlueprintGraphEdge,
|
|
29
38
|
type BlueprintGraphNode,
|
|
30
39
|
type BlueprintNodeRole,
|
|
31
40
|
} from "./graph/document";
|
|
@@ -42,6 +51,9 @@ export type BlueprintLibraryOption = {
|
|
|
42
51
|
|
|
43
52
|
export type BlueprintNodeConfigSidebarProps = {
|
|
44
53
|
node: BlueprintGraphNode;
|
|
54
|
+
graphNodes?: BlueprintGraphNode[];
|
|
55
|
+
graphEdges?: BlueprintGraphEdge[];
|
|
56
|
+
traceEntries?: ExecutionTraceEntry[];
|
|
45
57
|
viewElementOptions?: BlueprintViewElementOption[];
|
|
46
58
|
blueprintLibraryOptions?: BlueprintLibraryOption[];
|
|
47
59
|
allowFalseSignalPropagation?: boolean;
|
|
@@ -64,6 +76,7 @@ export type BlueprintNodeConfigSidebarProps = {
|
|
|
64
76
|
| "jsonConfig"
|
|
65
77
|
| "logicConfig"
|
|
66
78
|
| "clockConfig"
|
|
79
|
+
| "eventConfig"
|
|
67
80
|
>
|
|
68
81
|
>
|
|
69
82
|
) => void;
|
|
@@ -77,10 +90,14 @@ const ROLE_LABEL_KEYS: Record<BlueprintNodeRole, string> = {
|
|
|
77
90
|
json: "blueprint.config.roleJson",
|
|
78
91
|
logic: "blueprint.config.roleLogic",
|
|
79
92
|
clock: "blueprint.config.roleClock",
|
|
93
|
+
event: "blueprint.config.roleEvent",
|
|
80
94
|
};
|
|
81
95
|
|
|
82
96
|
export function BlueprintNodeConfigSidebar({
|
|
83
97
|
node,
|
|
98
|
+
graphNodes = [],
|
|
99
|
+
graphEdges = [],
|
|
100
|
+
traceEntries = [],
|
|
84
101
|
viewElementOptions = [],
|
|
85
102
|
blueprintLibraryOptions = [],
|
|
86
103
|
allowFalseSignalPropagation = false,
|
|
@@ -90,6 +107,7 @@ export function BlueprintNodeConfigSidebar({
|
|
|
90
107
|
const { t } = useI18n();
|
|
91
108
|
const configSource = resolveBlueprintConfigSource(node);
|
|
92
109
|
const linkedViewElementIds = resolveViewElementIds(node);
|
|
110
|
+
const selectedEventTypes = resolveNodeEventConfig(node).eventTypes;
|
|
93
111
|
const existingViewElementIdSet = useMemo(
|
|
94
112
|
() => new Set(viewElementOptions.map((opt) => opt.id)),
|
|
95
113
|
[viewElementOptions]
|
|
@@ -99,7 +117,7 @@ export function BlueprintNodeConfigSidebar({
|
|
|
99
117
|
);
|
|
100
118
|
|
|
101
119
|
useEffect(() => {
|
|
102
|
-
if (configSource !== "view") return;
|
|
120
|
+
if (configSource !== "view" && configSource !== "event") return;
|
|
103
121
|
// 视图画布尚未就绪时不清理,避免误删有效绑定导致绑定节点「执行了但不写 scope」
|
|
104
122
|
if (viewElementOptions.length === 0) return;
|
|
105
123
|
const linked = resolveViewElementIds(node);
|
|
@@ -109,7 +127,7 @@ export function BlueprintNodeConfigSidebar({
|
|
|
109
127
|
onUpdateNode(node.id, {
|
|
110
128
|
viewElementIds: pruned.length > 0 ? pruned : undefined,
|
|
111
129
|
viewElementId: undefined,
|
|
112
|
-
configSource
|
|
130
|
+
configSource,
|
|
113
131
|
});
|
|
114
132
|
}, [
|
|
115
133
|
configSource,
|
|
@@ -122,6 +140,10 @@ export function BlueprintNodeConfigSidebar({
|
|
|
122
140
|
]);
|
|
123
141
|
|
|
124
142
|
const roleLabel = t(ROLE_LABEL_KEYS[node.role] ?? "blueprint.config.roleLogic");
|
|
143
|
+
const eventInputs = useMemo(
|
|
144
|
+
() => inspectEventNodeInputs(node.id, graphNodes, graphEdges),
|
|
145
|
+
[graphEdges, graphNodes, node.id]
|
|
146
|
+
);
|
|
125
147
|
|
|
126
148
|
return (
|
|
127
149
|
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-background text-foreground">
|
|
@@ -151,7 +173,7 @@ export function BlueprintNodeConfigSidebar({
|
|
|
151
173
|
const nextSource = value as BlueprintConfigSource;
|
|
152
174
|
onUpdateNode(node.id, {
|
|
153
175
|
...patchNodeConfigSource(node, nextSource),
|
|
154
|
-
...(nextSource === "view"
|
|
176
|
+
...(nextSource === "view" || nextSource === "event"
|
|
155
177
|
? {
|
|
156
178
|
viewElementIds: linkedViewElementIds,
|
|
157
179
|
viewElementId: undefined,
|
|
@@ -174,14 +196,22 @@ export function BlueprintNodeConfigSidebar({
|
|
|
174
196
|
<SelectItem value="fetch">{t("blueprint.config.configFetch")}</SelectItem>
|
|
175
197
|
<SelectItem value="json">{t("blueprint.config.configJson")}</SelectItem>
|
|
176
198
|
<SelectItem value="clock">{t("blueprint.config.configClock")}</SelectItem>
|
|
199
|
+
<SelectItem value="event">{t("blueprint.config.configEvent")}</SelectItem>
|
|
177
200
|
<SelectItem value="view">{t("blueprint.config.configView")}</SelectItem>
|
|
178
201
|
</SelectContent>
|
|
179
202
|
</Select>
|
|
180
203
|
</label>
|
|
181
204
|
|
|
182
|
-
{configSource === "view" ? (
|
|
205
|
+
{configSource === "view" || configSource === "event" ? (
|
|
183
206
|
<div className="block space-y-1">
|
|
184
|
-
<
|
|
207
|
+
<ConfigFieldLabel
|
|
208
|
+
label={t("blueprint.config.linkedViewNodes")}
|
|
209
|
+
hint={
|
|
210
|
+
configSource === "event"
|
|
211
|
+
? t("blueprint.config.eventViewHint")
|
|
212
|
+
: t("blueprint.config.viewMultiHint")
|
|
213
|
+
}
|
|
214
|
+
/>
|
|
185
215
|
<ViewElementMultiSelect
|
|
186
216
|
options={viewElementOptions}
|
|
187
217
|
value={linkedViewElementIds}
|
|
@@ -190,15 +220,11 @@ export function BlueprintNodeConfigSidebar({
|
|
|
190
220
|
onUpdateNode(node.id, {
|
|
191
221
|
viewElementIds: next,
|
|
192
222
|
viewElementId: undefined,
|
|
193
|
-
configSource
|
|
223
|
+
configSource,
|
|
194
224
|
})
|
|
195
225
|
}
|
|
196
226
|
/>
|
|
197
|
-
{linkedViewElementIds.length
|
|
198
|
-
<p className="text-[11px] text-muted-foreground">
|
|
199
|
-
{t("blueprint.config.viewMultiHint")}
|
|
200
|
-
</p>
|
|
201
|
-
) : (
|
|
227
|
+
{linkedViewElementIds.length > 0 ? (
|
|
202
228
|
<p className="text-[11px] text-muted-foreground">
|
|
203
229
|
{t("blueprint.config.linkedViewCount", {
|
|
204
230
|
count: linkedViewElementIds.length,
|
|
@@ -207,16 +233,16 @@ export function BlueprintNodeConfigSidebar({
|
|
|
207
233
|
.join("、"),
|
|
208
234
|
})}
|
|
209
235
|
</p>
|
|
210
|
-
)}
|
|
236
|
+
) : null}
|
|
211
237
|
</div>
|
|
212
238
|
) : null}
|
|
213
239
|
|
|
214
240
|
{configSource === "blueprint" ? (
|
|
215
241
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
216
|
-
<
|
|
217
|
-
|
|
218
|
-
{t("blueprint.config.blueprintAttrsHint")}
|
|
219
|
-
|
|
242
|
+
<ConfigSectionTitle
|
|
243
|
+
title={t("blueprint.config.blueprintAttrs")}
|
|
244
|
+
hint={t("blueprint.config.blueprintAttrsHint")}
|
|
245
|
+
/>
|
|
220
246
|
<label className="block space-y-1">
|
|
221
247
|
<span className="text-muted-foreground">{t("blueprint.config.refLibrary")}</span>
|
|
222
248
|
<Select
|
|
@@ -262,15 +288,70 @@ export function BlueprintNodeConfigSidebar({
|
|
|
262
288
|
</div>
|
|
263
289
|
) : null}
|
|
264
290
|
|
|
291
|
+
{configSource === "event" ? (
|
|
292
|
+
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
293
|
+
<div className="flex items-center gap-1.5">
|
|
294
|
+
<div className="font-medium text-foreground">
|
|
295
|
+
{t("blueprint.config.eventListen")}
|
|
296
|
+
</div>
|
|
297
|
+
<ConfigHintIcon label={t("blueprint.config.eventListen")}>
|
|
298
|
+
<p>{t("blueprint.config.eventNoInput")}</p>
|
|
299
|
+
<p>{t("blueprint.config.eventHint")}</p>
|
|
300
|
+
</ConfigHintIcon>
|
|
301
|
+
</div>
|
|
302
|
+
{eventInputs.nonLifecycleCount > 0 ? (
|
|
303
|
+
<p className="rounded border border-destructive/40 bg-destructive/10 px-2 py-1.5 text-[11px] leading-relaxed text-destructive">
|
|
304
|
+
{t("blueprint.config.eventInputNotLifecycle")}
|
|
305
|
+
</p>
|
|
306
|
+
) : eventInputs.lifecycleCount === 0 ? (
|
|
307
|
+
<p className="rounded border border-amber-500/40 bg-amber-500/10 px-2 py-1.5 text-[11px] leading-relaxed text-amber-700 dark:text-amber-300">
|
|
308
|
+
{t("blueprint.config.eventInputMissing")}
|
|
309
|
+
</p>
|
|
310
|
+
) : null}
|
|
311
|
+
<div className="space-y-1.5">
|
|
312
|
+
{VIEW_EVENT_TYPES.map((eventType) => {
|
|
313
|
+
const checked = selectedEventTypes.includes(eventType);
|
|
314
|
+
return (
|
|
315
|
+
<label key={eventType} className="flex items-center gap-2">
|
|
316
|
+
<Checkbox
|
|
317
|
+
checked={checked}
|
|
318
|
+
className="h-3.5 w-3.5 border-2 border-foreground/80 bg-background ring-1 ring-foreground/40 data-[state=checked]:border-primary data-[state=checked]:ring-primary/40"
|
|
319
|
+
onCheckedChange={(nextChecked: boolean | "indeterminate") => {
|
|
320
|
+
const nextTypes: ViewEventType[] =
|
|
321
|
+
nextChecked === true
|
|
322
|
+
? [...new Set([...selectedEventTypes, eventType])]
|
|
323
|
+
: selectedEventTypes.filter((type) => type !== eventType);
|
|
324
|
+
onUpdateNode(node.id, {
|
|
325
|
+
eventConfig: {
|
|
326
|
+
eventTypes: nextTypes.length > 0 ? nextTypes : ["click"],
|
|
327
|
+
},
|
|
328
|
+
configSource: "event",
|
|
329
|
+
});
|
|
330
|
+
}}
|
|
331
|
+
/>
|
|
332
|
+
<span>{getViewEventTypeLabel(t, eventType)}</span>
|
|
333
|
+
</label>
|
|
334
|
+
);
|
|
335
|
+
})}
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
) : null}
|
|
339
|
+
|
|
265
340
|
{configSource === "lifecycle" ? (
|
|
266
341
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
267
|
-
<div className="
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
342
|
+
<div className="flex items-center gap-1.5">
|
|
343
|
+
<div className="font-medium text-foreground">
|
|
344
|
+
{t("blueprint.config.lifecycleHook")}
|
|
345
|
+
</div>
|
|
346
|
+
<ConfigHintIcon label={t("blueprint.config.lifecycleHook")}>
|
|
347
|
+
<p>{t("blueprint.config.lifecycleNoInput")}</p>
|
|
348
|
+
<p>
|
|
349
|
+
{node.lifecyclePhase === "blueprintActivated"
|
|
350
|
+
? t("blueprint.config.lifecycleBlueprintActivatedHint")
|
|
351
|
+
: t("blueprint.config.lifecyclePageHint")}
|
|
352
|
+
</p>
|
|
353
|
+
</ConfigHintIcon>
|
|
354
|
+
</div>
|
|
274
355
|
<label className="block space-y-1">
|
|
275
356
|
<span className="text-muted-foreground">{t("blueprint.config.listenPhase")}</span>
|
|
276
357
|
<Select
|
|
@@ -305,7 +386,13 @@ export function BlueprintNodeConfigSidebar({
|
|
|
305
386
|
) : null}
|
|
306
387
|
|
|
307
388
|
{configSource === "fetch" ? (
|
|
308
|
-
<FetchNodeConfigPanel
|
|
389
|
+
<FetchNodeConfigPanel
|
|
390
|
+
node={node}
|
|
391
|
+
graphNodes={graphNodes}
|
|
392
|
+
graphEdges={graphEdges}
|
|
393
|
+
traceEntries={traceEntries}
|
|
394
|
+
onUpdateNode={onUpdateNode}
|
|
395
|
+
/>
|
|
309
396
|
) : null}
|
|
310
397
|
|
|
311
398
|
{configSource === "json" ? (
|
|
@@ -318,10 +405,10 @@ export function BlueprintNodeConfigSidebar({
|
|
|
318
405
|
|
|
319
406
|
{configSource === "and" ? (
|
|
320
407
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
{t("blueprint.config.andHint")}
|
|
324
|
-
|
|
408
|
+
<ConfigSectionTitle
|
|
409
|
+
title={t("blueprint.config.andTitle")}
|
|
410
|
+
hint={t("blueprint.config.andHint")}
|
|
411
|
+
/>
|
|
325
412
|
</div>
|
|
326
413
|
) : null}
|
|
327
414
|
|
|
@@ -6,6 +6,7 @@ import { ClockFlowNode } from "./nodes/ClockFlowNode";
|
|
|
6
6
|
import { FetchFlowNode } from "./nodes/FetchFlowNode";
|
|
7
7
|
import { JsonFlowNode } from "./nodes/JsonFlowNode";
|
|
8
8
|
import { LifecycleFlowNode } from "./nodes/LifecycleFlowNode";
|
|
9
|
+
import { EventFlowNode } from "./nodes/EventFlowNode";
|
|
9
10
|
import { LogicFlowNode } from "./nodes/LogicFlowNode";
|
|
10
11
|
|
|
11
12
|
/** 稳定引用,避免 React Flow 因 nodeTypes 变化反复卸载节点 */
|
|
@@ -17,4 +18,5 @@ export const blueprintNodeTypes = {
|
|
|
17
18
|
json: JsonFlowNode,
|
|
18
19
|
logic: LogicFlowNode,
|
|
19
20
|
lifecycle: LifecycleFlowNode,
|
|
21
|
+
event: EventFlowNode,
|
|
20
22
|
} satisfies NodeTypes;
|
|
@@ -5,6 +5,7 @@ import { useI18n } from "@arronqzy/i18n/react";
|
|
|
5
5
|
|
|
6
6
|
import type { BlueprintGraphNode } from "../graph/document";
|
|
7
7
|
import { resolveNodeClockConfig } from "../graph/document";
|
|
8
|
+
import { ConfigFieldLabel, ConfigSectionTitle } from "./ConfigHintIcon";
|
|
8
9
|
|
|
9
10
|
export type ClockNodeConfigPanelProps = {
|
|
10
11
|
node: BlueprintGraphNode;
|
|
@@ -73,17 +74,16 @@ export function ClockNodeConfigPanel({
|
|
|
73
74
|
|
|
74
75
|
return (
|
|
75
76
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
76
|
-
<
|
|
77
|
-
{t("blueprint.config.clockTitle")}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{t("blueprint.config.clockHint")}
|
|
81
|
-
</p>
|
|
77
|
+
<ConfigSectionTitle
|
|
78
|
+
title={t("blueprint.config.clockTitle")}
|
|
79
|
+
hint={t("blueprint.config.clockHint")}
|
|
80
|
+
/>
|
|
82
81
|
|
|
83
82
|
<label className="block space-y-1">
|
|
84
|
-
<
|
|
85
|
-
{t("blueprint.config.clockIntervalSeconds")}
|
|
86
|
-
|
|
83
|
+
<ConfigFieldLabel
|
|
84
|
+
label={t("blueprint.config.clockIntervalSeconds")}
|
|
85
|
+
hint={t("blueprint.config.clockIntervalHint")}
|
|
86
|
+
/>
|
|
87
87
|
<Input
|
|
88
88
|
type="number"
|
|
89
89
|
min={0}
|
|
@@ -92,15 +92,13 @@ export function ClockNodeConfigPanel({
|
|
|
92
92
|
onChange={handleIntervalChange}
|
|
93
93
|
className="h-8"
|
|
94
94
|
/>
|
|
95
|
-
<p className="text-[11px] text-muted-foreground">
|
|
96
|
-
{t("blueprint.config.clockIntervalHint")}
|
|
97
|
-
</p>
|
|
98
95
|
</label>
|
|
99
96
|
|
|
100
97
|
<label className="block space-y-1">
|
|
101
|
-
<
|
|
102
|
-
{t("blueprint.config.outputCount")}
|
|
103
|
-
|
|
98
|
+
<ConfigFieldLabel
|
|
99
|
+
label={t("blueprint.config.outputCount")}
|
|
100
|
+
hint={t("blueprint.config.outputCountHint")}
|
|
101
|
+
/>
|
|
104
102
|
<Input
|
|
105
103
|
type="number"
|
|
106
104
|
min={1}
|
|
@@ -109,9 +107,6 @@ export function ClockNodeConfigPanel({
|
|
|
109
107
|
onChange={handleOutputCountChange}
|
|
110
108
|
className="h-8"
|
|
111
109
|
/>
|
|
112
|
-
<p className="text-[11px] text-muted-foreground">
|
|
113
|
-
{t("blueprint.config.outputCountHint")}
|
|
114
|
-
</p>
|
|
115
110
|
</label>
|
|
116
111
|
|
|
117
112
|
<label className="flex items-start gap-2">
|
|
@@ -127,9 +122,10 @@ export function ClockNodeConfigPanel({
|
|
|
127
122
|
</label>
|
|
128
123
|
|
|
129
124
|
<label className="block space-y-1">
|
|
130
|
-
<
|
|
131
|
-
{t("blueprint.config.timeFormat")}
|
|
132
|
-
|
|
125
|
+
<ConfigFieldLabel
|
|
126
|
+
label={t("blueprint.config.timeFormat")}
|
|
127
|
+
hint={t("blueprint.config.timeFormatHint")}
|
|
128
|
+
/>
|
|
133
129
|
<Input
|
|
134
130
|
value={clockConfig.timeFormat}
|
|
135
131
|
onChange={handleFormatChange}
|
|
@@ -137,9 +133,6 @@ export function ClockNodeConfigPanel({
|
|
|
137
133
|
className="h-8 font-mono text-[11px]"
|
|
138
134
|
placeholder="YYYY-MM-DD HH:mm:ss"
|
|
139
135
|
/>
|
|
140
|
-
<p className="text-[11px] text-muted-foreground">
|
|
141
|
-
{t("blueprint.config.timeFormatHint")}
|
|
142
|
-
</p>
|
|
143
136
|
</label>
|
|
144
137
|
</div>
|
|
145
138
|
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
|
+
import {
|
|
4
|
+
Tooltip,
|
|
5
|
+
TooltipContent,
|
|
6
|
+
TooltipProvider,
|
|
7
|
+
TooltipTrigger,
|
|
8
|
+
} from "@arronqzy/ui";
|
|
9
|
+
|
|
10
|
+
export type ConfigHintIconProps = {
|
|
11
|
+
label?: string;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
className?: string;
|
|
14
|
+
contentClassName?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function ConfigHintIcon({
|
|
18
|
+
label,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
contentClassName,
|
|
22
|
+
}: ConfigHintIconProps) {
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
const resolvedLabel = label ?? t("common.hint");
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<TooltipProvider delayDuration={120}>
|
|
28
|
+
<Tooltip>
|
|
29
|
+
<TooltipTrigger asChild>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
className={
|
|
33
|
+
className ??
|
|
34
|
+
"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-border text-[10px] leading-none text-muted-foreground hover:bg-accent/50"
|
|
35
|
+
}
|
|
36
|
+
aria-label={t("common.hintAria", { label: resolvedLabel })}
|
|
37
|
+
>
|
|
38
|
+
?
|
|
39
|
+
</button>
|
|
40
|
+
</TooltipTrigger>
|
|
41
|
+
<TooltipContent
|
|
42
|
+
side="top"
|
|
43
|
+
className={`z-[10120] max-w-[360px] space-y-1.5 text-[11px] leading-5 ${contentClassName ?? ""}`}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</TooltipContent>
|
|
47
|
+
</Tooltip>
|
|
48
|
+
</TooltipProvider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function ConfigSectionTitle({
|
|
53
|
+
title,
|
|
54
|
+
hint,
|
|
55
|
+
}: {
|
|
56
|
+
title: string;
|
|
57
|
+
hint: ReactNode;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex items-center gap-1.5">
|
|
61
|
+
<div className="font-medium text-foreground">{title}</div>
|
|
62
|
+
<ConfigHintIcon label={title}>{hint}</ConfigHintIcon>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function ConfigFieldLabel({
|
|
68
|
+
label,
|
|
69
|
+
hint,
|
|
70
|
+
}: {
|
|
71
|
+
label: string;
|
|
72
|
+
hint?: ReactNode;
|
|
73
|
+
}) {
|
|
74
|
+
if (!hint) {
|
|
75
|
+
return <span className="text-muted-foreground">{label}</span>;
|
|
76
|
+
}
|
|
77
|
+
return (
|
|
78
|
+
<span className="inline-flex items-center gap-1 text-muted-foreground">
|
|
79
|
+
{label}
|
|
80
|
+
<ConfigHintIcon label={label}>{hint}</ConfigHintIcon>
|
|
81
|
+
</span>
|
|
82
|
+
);
|
|
83
|
+
}
|