@international-iot-association/ui 1.0.0-rc.1

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 ADDED
@@ -0,0 +1,4 @@
1
+ # @international-iot-association/ui
2
+
3
+ 工位插件 UI 共用的 shadcn/ui(new-york、zinc)组件与深色主题;含 `styles.css` 独立导出。
4
+ 属于**组 B · 独立版本组**(不跟随 `agentApi` major,各自 semver)。
package/dist/index.cjs ADDED
@@ -0,0 +1,493 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Badge: () => Badge,
34
+ Button: () => Button,
35
+ Card: () => Card,
36
+ CardContent: () => CardContent,
37
+ CardDescription: () => CardDescription,
38
+ CardFooter: () => CardFooter,
39
+ CardHeader: () => CardHeader,
40
+ CardTitle: () => CardTitle,
41
+ MeasurementRow: () => MeasurementRow,
42
+ PrimaryAction: () => PrimaryAction,
43
+ ScanInput: () => ScanInput,
44
+ StationStatePanel: () => StationStatePanel,
45
+ StepRunner: () => StepRunner,
46
+ badgeVariants: () => badgeVariants,
47
+ buttonVariants: () => buttonVariants,
48
+ cn: () => cn,
49
+ primaryActionVariants: () => primaryActionVariants,
50
+ stationStatePanelVariants: () => panelVariants
51
+ });
52
+ module.exports = __toCommonJS(index_exports);
53
+
54
+ // src/lib/utils.ts
55
+ var import_clsx = require("clsx");
56
+ var import_tailwind_merge = require("tailwind-merge");
57
+ function cn(...inputs) {
58
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
59
+ }
60
+
61
+ // src/components/button.tsx
62
+ var import_react_slot = require("@radix-ui/react-slot");
63
+ var import_class_variance_authority = require("class-variance-authority");
64
+ var import_jsx_runtime = require("react/jsx-runtime");
65
+ var buttonVariants = (0, import_class_variance_authority.cva)(
66
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
67
+ {
68
+ variants: {
69
+ variant: {
70
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
71
+ destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
72
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
73
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
74
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
75
+ link: "text-primary underline-offset-4 hover:underline"
76
+ },
77
+ size: {
78
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
79
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
80
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
81
+ icon: "size-9"
82
+ }
83
+ },
84
+ defaultVariants: {
85
+ variant: "default",
86
+ size: "default"
87
+ }
88
+ }
89
+ );
90
+ function Button({
91
+ className,
92
+ variant,
93
+ size,
94
+ asChild = false,
95
+ ...props
96
+ }) {
97
+ const Comp = asChild ? import_react_slot.Slot : "button";
98
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
99
+ Comp,
100
+ {
101
+ "data-slot": "button",
102
+ className: cn(buttonVariants({ variant, size, className })),
103
+ ...props
104
+ }
105
+ );
106
+ }
107
+
108
+ // src/components/card.tsx
109
+ var import_jsx_runtime2 = require("react/jsx-runtime");
110
+ function Card({ className, ...props }) {
111
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
112
+ "div",
113
+ {
114
+ "data-slot": "card",
115
+ className: cn(
116
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
117
+ className
118
+ ),
119
+ ...props
120
+ }
121
+ );
122
+ }
123
+ function CardHeader({ className, ...props }) {
124
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
125
+ "div",
126
+ {
127
+ "data-slot": "card-header",
128
+ className: cn(
129
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
130
+ className
131
+ ),
132
+ ...props
133
+ }
134
+ );
135
+ }
136
+ function CardTitle({ className, ...props }) {
137
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
138
+ "div",
139
+ {
140
+ "data-slot": "card-title",
141
+ className: cn("leading-none font-semibold", className),
142
+ ...props
143
+ }
144
+ );
145
+ }
146
+ function CardDescription({ className, ...props }) {
147
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
148
+ "div",
149
+ {
150
+ "data-slot": "card-description",
151
+ className: cn("text-muted-foreground text-sm", className),
152
+ ...props
153
+ }
154
+ );
155
+ }
156
+ function CardContent({ className, ...props }) {
157
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-slot": "card-content", className: cn("px-6", className), ...props });
158
+ }
159
+ function CardFooter({ className, ...props }) {
160
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
161
+ "div",
162
+ {
163
+ "data-slot": "card-footer",
164
+ className: cn("flex items-center px-6 [.border-t]:pt-6", className),
165
+ ...props
166
+ }
167
+ );
168
+ }
169
+
170
+ // src/components/badge.tsx
171
+ var import_react_slot2 = require("@radix-ui/react-slot");
172
+ var import_class_variance_authority2 = require("class-variance-authority");
173
+ var import_jsx_runtime3 = require("react/jsx-runtime");
174
+ var badgeVariants = (0, import_class_variance_authority2.cva)(
175
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
176
+ {
177
+ variants: {
178
+ variant: {
179
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
180
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
181
+ destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
182
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
183
+ success: "border-transparent bg-emerald-600 text-white"
184
+ }
185
+ },
186
+ defaultVariants: {
187
+ variant: "default"
188
+ }
189
+ }
190
+ );
191
+ function Badge({
192
+ className,
193
+ variant,
194
+ asChild = false,
195
+ ...props
196
+ }) {
197
+ const Comp = asChild ? import_react_slot2.Slot : "span";
198
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Comp, { "data-slot": "badge", className: cn(badgeVariants({ variant }), className), ...props });
199
+ }
200
+
201
+ // src/components/step-runner.tsx
202
+ var React = __toESM(require("react"), 1);
203
+ var import_jsx_runtime4 = require("react/jsx-runtime");
204
+ function StepRunner({
205
+ steps,
206
+ emptyHint
207
+ }) {
208
+ const [records, setRecords] = React.useState([]);
209
+ const [busy, setBusy] = React.useState(null);
210
+ const onRun = async (step) => {
211
+ setBusy(step.key);
212
+ try {
213
+ const r = await step.run();
214
+ setRecords((prev) => [{ ...r, ts: (/* @__PURE__ */ new Date()).toISOString() }, ...prev].slice(0, 50));
215
+ } catch (e) {
216
+ setRecords(
217
+ (prev) => [
218
+ {
219
+ ok: false,
220
+ stepKey: step.key,
221
+ error: e instanceof Error ? e.message : String(e),
222
+ ts: (/* @__PURE__ */ new Date()).toISOString()
223
+ },
224
+ ...prev
225
+ ].slice(0, 50)
226
+ );
227
+ } finally {
228
+ setBusy(null);
229
+ }
230
+ };
231
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-3", children: [
232
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex flex-wrap gap-2", children: steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
233
+ Button,
234
+ {
235
+ variant: "secondary",
236
+ size: "sm",
237
+ disabled: busy !== null,
238
+ title: s.description,
239
+ onClick: () => void onRun(s),
240
+ children: busy === s.key ? "\u8FD0\u884C\u4E2D\u2026" : s.label
241
+ },
242
+ s.key
243
+ )) }),
244
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
245
+ records.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rounded-md bg-muted/30 px-2 py-3 text-center text-xs text-muted-foreground", children: emptyHint ?? "\u70B9\u51FB\u4E0A\u65B9\u6309\u94AE\u6267\u884C\u6D4B\u8BD5\u6B65\u9AA4\u3002" }),
246
+ records.map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
247
+ "div",
248
+ {
249
+ className: cn(
250
+ "rounded-md border p-2 text-xs",
251
+ r.ok ? "border-emerald-500/30 bg-emerald-500/5" : "border-destructive/30 bg-destructive/5"
252
+ ),
253
+ children: [
254
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", children: [
255
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
256
+ "span",
257
+ {
258
+ className: cn(
259
+ "rounded px-1.5 py-0.5 text-[10px] font-semibold",
260
+ r.ok ? "bg-emerald-500/20 text-emerald-400" : "bg-destructive/20 text-destructive"
261
+ ),
262
+ children: r.ok ? "PASS" : "FAIL"
263
+ }
264
+ ),
265
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "font-medium", children: r.stepKey }),
266
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: r.ts.slice(11, 19) })
267
+ ] }),
268
+ r.measurements && Object.keys(r.measurements).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pre", { className: "mt-1.5 overflow-x-auto rounded bg-muted/50 p-1.5 font-mono text-[10px]", children: JSON.stringify(r.measurements) }),
269
+ r.error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mt-1 text-destructive", children: r.error })
270
+ ]
271
+ },
272
+ i
273
+ ))
274
+ ] })
275
+ ] });
276
+ }
277
+
278
+ // src/components/station-state-panel.tsx
279
+ var import_class_variance_authority3 = require("class-variance-authority");
280
+ var import_jsx_runtime5 = require("react/jsx-runtime");
281
+ var STATE_META = {
282
+ idle: { word: "\u5F85\u673A", hint: "\u7B49\u5F85\u626B\u7801" },
283
+ running: { word: "\u6D4B\u8BD5\u4E2D", hint: "\u8BF7\u52FF\u79FB\u52A8\u4EA7\u54C1" },
284
+ pass: { word: "\u2713 PASS", hint: "\u5408\u683C" },
285
+ fail: { word: "\u2715 FAIL", hint: "\u4E0D\u5408\u683C" },
286
+ fault: { word: "\u26A0 FAULT", hint: "\u5DE5\u4F4D\u6545\u969C" }
287
+ };
288
+ var panelVariants = (0, import_class_variance_authority3.cva)(
289
+ "flex min-h-[12rem] w-full flex-col items-center justify-center gap-[0.5rem] rounded-lg border-2 px-[1.5rem] py-[2rem] text-center",
290
+ {
291
+ variants: {
292
+ state: {
293
+ idle: "border-border bg-card text-muted-foreground",
294
+ running: "border-sky-500/60 bg-sky-500/10 text-sky-400",
295
+ pass: "border-emerald-500/60 bg-emerald-500/10 text-emerald-400",
296
+ fail: "border-red-500/60 bg-red-500/10 text-red-400",
297
+ fault: "border-amber-500/60 bg-amber-500/10 text-amber-400"
298
+ }
299
+ }
300
+ }
301
+ );
302
+ function StationStatePanel({
303
+ state,
304
+ title,
305
+ detail,
306
+ className,
307
+ ...props
308
+ }) {
309
+ const meta = STATE_META[state];
310
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
311
+ "div",
312
+ {
313
+ "data-slot": "station-state-panel",
314
+ role: "status",
315
+ "aria-live": "polite",
316
+ className: cn(panelVariants({ state }), className),
317
+ ...props,
318
+ children: [
319
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
320
+ "span",
321
+ {
322
+ className: cn(
323
+ "text-[3.75rem] leading-none font-bold tracking-tight",
324
+ state === "running" && "animate-pulse"
325
+ ),
326
+ children: title ?? meta.word
327
+ }
328
+ ),
329
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[1.125rem] font-medium opacity-80", children: detail ?? meta.hint })
330
+ ]
331
+ }
332
+ );
333
+ }
334
+
335
+ // src/components/measurement-row.tsx
336
+ var import_jsx_runtime6 = require("react/jsx-runtime");
337
+ var VERDICT_META = {
338
+ pass: { text: "\u2713 PASS", className: "border-emerald-500/50 bg-emerald-500/10 text-emerald-400" },
339
+ fail: { text: "\u2715 FAIL", className: "border-red-500/50 bg-red-500/10 text-red-400" },
340
+ pending: { text: "\u2026", className: "border-border bg-secondary text-muted-foreground" }
341
+ };
342
+ function formatSpec(lo, hi, unit) {
343
+ if (lo === void 0 && hi === void 0) return "";
344
+ const u = unit ? ` ${unit}` : "";
345
+ if (lo !== void 0 && hi !== void 0) return `\u89C4\u683C\uFF1A${lo} ~ ${hi}${u}`;
346
+ if (lo !== void 0) return `\u89C4\u683C\uFF1A\u2265 ${lo}${u}`;
347
+ return `\u89C4\u683C\uFF1A\u2264 ${hi}${u}`;
348
+ }
349
+ function MeasurementRow({
350
+ label,
351
+ value,
352
+ unit,
353
+ lo,
354
+ hi,
355
+ verdict = "pending",
356
+ className,
357
+ ...props
358
+ }) {
359
+ const meta = VERDICT_META[verdict];
360
+ const spec = formatSpec(lo, hi, unit);
361
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
362
+ "div",
363
+ {
364
+ "data-slot": "measurement-row",
365
+ className: cn(
366
+ "flex min-h-[3.5rem] w-full items-center gap-[1rem] rounded-md border bg-card px-[1rem] py-[0.5rem]",
367
+ verdict === "fail" && "border-red-500/50",
368
+ className
369
+ ),
370
+ ...props,
371
+ children: [
372
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "min-w-[6rem] shrink-0 text-[1rem] font-medium", children: label }),
373
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "font-mono text-[1.5rem] font-semibold tabular-nums", children: [
374
+ value ?? "--",
375
+ unit ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ml-[0.375rem] text-[1rem] font-normal text-muted-foreground", children: unit }) : null
376
+ ] }),
377
+ spec ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ml-auto hidden shrink-0 font-mono text-[0.875rem] text-muted-foreground tabular-nums sm:inline", children: spec }) : null,
378
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
379
+ "span",
380
+ {
381
+ className: cn(
382
+ "inline-flex min-w-[5rem] shrink-0 items-center justify-center rounded-md border px-[0.625rem] py-[0.375rem] text-[0.9375rem] font-semibold",
383
+ !spec && "ml-auto",
384
+ meta.className
385
+ ),
386
+ children: meta.text
387
+ }
388
+ )
389
+ ]
390
+ }
391
+ );
392
+ }
393
+
394
+ // src/components/primary-action.tsx
395
+ var import_class_variance_authority4 = require("class-variance-authority");
396
+ var import_jsx_runtime7 = require("react/jsx-runtime");
397
+ var primaryActionVariants = (0, import_class_variance_authority4.cva)("w-full font-semibold", {
398
+ variants: {
399
+ size: {
400
+ lg: "h-[3rem] rounded-lg px-[1.5rem] text-[1.25rem]",
401
+ xl: "h-[4.5rem] rounded-lg px-[2rem] text-[1.5rem]"
402
+ }
403
+ },
404
+ defaultVariants: {
405
+ size: "lg"
406
+ }
407
+ });
408
+ function PrimaryAction({
409
+ className,
410
+ size,
411
+ destructive = false,
412
+ ...props
413
+ }) {
414
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
415
+ "button",
416
+ {
417
+ "data-slot": "primary-action",
418
+ className: cn(
419
+ buttonVariants({ variant: destructive ? "destructive" : "default" }),
420
+ primaryActionVariants({ size }),
421
+ className
422
+ ),
423
+ ...props
424
+ }
425
+ );
426
+ }
427
+
428
+ // src/components/scan-input.tsx
429
+ var React2 = __toESM(require("react"), 1);
430
+ var import_jsx_runtime8 = require("react/jsx-runtime");
431
+ function ScanInput({
432
+ label,
433
+ value,
434
+ onChange,
435
+ onSubmit,
436
+ className,
437
+ disabled,
438
+ ...props
439
+ }) {
440
+ const inputRef = React2.useRef(null);
441
+ const handleKeyDown = (e) => {
442
+ if (e.key !== "Enter") return;
443
+ const trimmed = value.trim();
444
+ if (trimmed === "") return;
445
+ onSubmit(trimmed);
446
+ inputRef.current?.select();
447
+ };
448
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "flex w-full flex-col gap-[0.375rem]", children: [
449
+ label ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[0.875rem] font-medium text-muted-foreground", children: label }) : null,
450
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
451
+ "input",
452
+ {
453
+ ref: inputRef,
454
+ "data-slot": "scan-input",
455
+ type: "text",
456
+ autoComplete: "off",
457
+ spellCheck: false,
458
+ value,
459
+ disabled,
460
+ onChange: (e) => onChange(e.target.value),
461
+ onKeyDown: handleKeyDown,
462
+ className: cn(
463
+ "h-[2.75rem] w-full rounded-md border bg-background px-[0.875rem] font-mono text-[1.125rem] tracking-wide",
464
+ "outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50",
465
+ "disabled:pointer-events-none disabled:opacity-50",
466
+ className
467
+ ),
468
+ ...props
469
+ }
470
+ )
471
+ ] });
472
+ }
473
+ // Annotate the CommonJS export names for ESM import in node:
474
+ 0 && (module.exports = {
475
+ Badge,
476
+ Button,
477
+ Card,
478
+ CardContent,
479
+ CardDescription,
480
+ CardFooter,
481
+ CardHeader,
482
+ CardTitle,
483
+ MeasurementRow,
484
+ PrimaryAction,
485
+ ScanInput,
486
+ StationStatePanel,
487
+ StepRunner,
488
+ badgeVariants,
489
+ buttonVariants,
490
+ cn,
491
+ primaryActionVariants,
492
+ stationStatePanelVariants
493
+ });
@@ -0,0 +1,117 @@
1
+ import { ClassValue } from 'clsx';
2
+ import * as class_variance_authority_types from 'class-variance-authority/types';
3
+ import * as React from 'react';
4
+ import { VariantProps } from 'class-variance-authority';
5
+
6
+ declare function cn(...inputs: ClassValue[]): string;
7
+
8
+ declare const buttonVariants: (props?: ({
9
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
10
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined;
11
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
12
+ declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
13
+ asChild?: boolean;
14
+ }): React.JSX.Element;
15
+
16
+ declare function Card({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
17
+ declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
18
+ declare function CardTitle({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
19
+ declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
20
+ declare function CardContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
21
+ declare function CardFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
22
+
23
+ declare const badgeVariants: (props?: ({
24
+ variant?: "default" | "destructive" | "outline" | "secondary" | "success" | null | undefined;
25
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
26
+ declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
27
+ asChild?: boolean;
28
+ }): React.JSX.Element;
29
+
30
+ /** 一次步骤运行所解析出的最小结果结构(StepResult 的子集)。 */
31
+ interface StepRunResult {
32
+ ok: boolean;
33
+ stepKey: string;
34
+ measurements?: Record<string, unknown>;
35
+ error?: string;
36
+ }
37
+ interface StepDef {
38
+ key: string;
39
+ label: string;
40
+ description?: string;
41
+ /** 执行该步骤;解析为其结果(通常通过一次 API-Bridge 调用得到)。 */
42
+ run: () => Promise<StepRunResult>;
43
+ }
44
+ /**
45
+ * 通用的测试工位步骤运行器:一排步骤按钮 + 一个滚动的 PASS/FAIL 结果列表。
46
+ * 由插件提供具体步骤;每个 `run()` 通常是一次进入插件 runtime 的 API-Bridge 调用。
47
+ */
48
+ declare function StepRunner({ steps, emptyHint, }: {
49
+ steps: StepDef[];
50
+ emptyHint?: string;
51
+ }): React.JSX.Element;
52
+
53
+ /**
54
+ * 工位大状态面板:操作员在 2–3 米外一眼可读的当前工位状态。
55
+ * 颜色永远配合文字(不单独用颜色传递信息);全部 rem 尺寸,随根字号缩放。
56
+ */
57
+ type StationState = "idle" | "running" | "pass" | "fail" | "fault";
58
+ declare const panelVariants: (props?: ({
59
+ state?: "idle" | "running" | "pass" | "fail" | "fault" | null | undefined;
60
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
61
+ interface StationStatePanelProps extends Omit<React.ComponentProps<"div">, "children">, VariantProps<typeof panelVariants> {
62
+ state: StationState;
63
+ /** 覆盖默认状态词(如 FAIL 时显示失败项名)。 */
64
+ title?: string;
65
+ /** 覆盖默认说明(如下一步操作指令)。 */
66
+ detail?: string;
67
+ }
68
+ declare function StationStatePanel({ state, title, detail, className, ...props }: StationStatePanelProps): React.JSX.Element;
69
+
70
+ /**
71
+ * 测量结果行:实测值 + 单位 + 规格上下限 + 结论。
72
+ * 比单行 "EVM Error" 有用:超限时操作员能直接看到实测值与规格的差距。
73
+ * 数值使用 tabular-nums 保证逐帧刷新时不跳动;全部 rem 尺寸。
74
+ */
75
+ type MeasurementVerdict = "pass" | "fail" | "pending";
76
+ interface MeasurementRowProps extends Omit<React.ComponentProps<"div">, "children"> {
77
+ label: string;
78
+ /** 实测值;null 表示尚未测得(显示占位)。 */
79
+ value: number | string | null;
80
+ unit?: string;
81
+ /** 规格下限/上限(缺省表示单边或无规格)。 */
82
+ lo?: number;
83
+ hi?: number;
84
+ verdict?: MeasurementVerdict;
85
+ }
86
+ declare function MeasurementRow({ label, value, unit, lo, hi, verdict, className, ...props }: MeasurementRowProps): React.JSX.Element;
87
+
88
+ /**
89
+ * 产线主操作按钮:每个工位状态只保留一个主动作。
90
+ * lg = 3rem 高(高频主操作最小推荐),xl = 4.5rem(超大工位/厚手套)。
91
+ * 默认全宽;全部 rem 尺寸,随根字号(校准基准 × 模式系数)缩放。
92
+ */
93
+ declare const primaryActionVariants: (props?: ({
94
+ size?: "lg" | "xl" | null | undefined;
95
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
96
+ interface PrimaryActionProps extends React.ComponentProps<"button">, VariantProps<typeof primaryActionVariants> {
97
+ /** 危险/终止类动作(红色)。 */
98
+ destructive?: boolean;
99
+ }
100
+ declare function PrimaryAction({ className, size, destructive, ...props }: PrimaryActionProps): React.JSX.Element;
101
+
102
+ /**
103
+ * 条码扫描输入:产线流程的起点。
104
+ * - 2.75rem 最小触摸高度(校准后 ≈11.6mm 物理高度);
105
+ * - Enter 提交后全选,扫码枪连续扫码无需手动清空;
106
+ * - mono 字体 + 大字号,SN 逐字符可核对。
107
+ */
108
+ interface ScanInputProps extends Omit<React.ComponentProps<"input">, "onSubmit" | "onChange" | "value"> {
109
+ label?: string;
110
+ value: string;
111
+ onChange: (value: string) => void;
112
+ /** Enter(扫码枪回车后缀)触发;空值不触发。 */
113
+ onSubmit: (value: string) => void;
114
+ }
115
+ declare function ScanInput({ label, value, onChange, onSubmit, className, disabled, ...props }: ScanInputProps): React.JSX.Element;
116
+
117
+ export { Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, MeasurementRow, type MeasurementRowProps, type MeasurementVerdict, PrimaryAction, type PrimaryActionProps, ScanInput, type ScanInputProps, type StationState, StationStatePanel, type StationStatePanelProps, type StepDef, type StepRunResult, StepRunner, badgeVariants, buttonVariants, cn, primaryActionVariants, panelVariants as stationStatePanelVariants };
@@ -0,0 +1,117 @@
1
+ import { ClassValue } from 'clsx';
2
+ import * as class_variance_authority_types from 'class-variance-authority/types';
3
+ import * as React from 'react';
4
+ import { VariantProps } from 'class-variance-authority';
5
+
6
+ declare function cn(...inputs: ClassValue[]): string;
7
+
8
+ declare const buttonVariants: (props?: ({
9
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
10
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined;
11
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
12
+ declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
13
+ asChild?: boolean;
14
+ }): React.JSX.Element;
15
+
16
+ declare function Card({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
17
+ declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
18
+ declare function CardTitle({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
19
+ declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
20
+ declare function CardContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
21
+ declare function CardFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
22
+
23
+ declare const badgeVariants: (props?: ({
24
+ variant?: "default" | "destructive" | "outline" | "secondary" | "success" | null | undefined;
25
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
26
+ declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
27
+ asChild?: boolean;
28
+ }): React.JSX.Element;
29
+
30
+ /** 一次步骤运行所解析出的最小结果结构(StepResult 的子集)。 */
31
+ interface StepRunResult {
32
+ ok: boolean;
33
+ stepKey: string;
34
+ measurements?: Record<string, unknown>;
35
+ error?: string;
36
+ }
37
+ interface StepDef {
38
+ key: string;
39
+ label: string;
40
+ description?: string;
41
+ /** 执行该步骤;解析为其结果(通常通过一次 API-Bridge 调用得到)。 */
42
+ run: () => Promise<StepRunResult>;
43
+ }
44
+ /**
45
+ * 通用的测试工位步骤运行器:一排步骤按钮 + 一个滚动的 PASS/FAIL 结果列表。
46
+ * 由插件提供具体步骤;每个 `run()` 通常是一次进入插件 runtime 的 API-Bridge 调用。
47
+ */
48
+ declare function StepRunner({ steps, emptyHint, }: {
49
+ steps: StepDef[];
50
+ emptyHint?: string;
51
+ }): React.JSX.Element;
52
+
53
+ /**
54
+ * 工位大状态面板:操作员在 2–3 米外一眼可读的当前工位状态。
55
+ * 颜色永远配合文字(不单独用颜色传递信息);全部 rem 尺寸,随根字号缩放。
56
+ */
57
+ type StationState = "idle" | "running" | "pass" | "fail" | "fault";
58
+ declare const panelVariants: (props?: ({
59
+ state?: "idle" | "running" | "pass" | "fail" | "fault" | null | undefined;
60
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
61
+ interface StationStatePanelProps extends Omit<React.ComponentProps<"div">, "children">, VariantProps<typeof panelVariants> {
62
+ state: StationState;
63
+ /** 覆盖默认状态词(如 FAIL 时显示失败项名)。 */
64
+ title?: string;
65
+ /** 覆盖默认说明(如下一步操作指令)。 */
66
+ detail?: string;
67
+ }
68
+ declare function StationStatePanel({ state, title, detail, className, ...props }: StationStatePanelProps): React.JSX.Element;
69
+
70
+ /**
71
+ * 测量结果行:实测值 + 单位 + 规格上下限 + 结论。
72
+ * 比单行 "EVM Error" 有用:超限时操作员能直接看到实测值与规格的差距。
73
+ * 数值使用 tabular-nums 保证逐帧刷新时不跳动;全部 rem 尺寸。
74
+ */
75
+ type MeasurementVerdict = "pass" | "fail" | "pending";
76
+ interface MeasurementRowProps extends Omit<React.ComponentProps<"div">, "children"> {
77
+ label: string;
78
+ /** 实测值;null 表示尚未测得(显示占位)。 */
79
+ value: number | string | null;
80
+ unit?: string;
81
+ /** 规格下限/上限(缺省表示单边或无规格)。 */
82
+ lo?: number;
83
+ hi?: number;
84
+ verdict?: MeasurementVerdict;
85
+ }
86
+ declare function MeasurementRow({ label, value, unit, lo, hi, verdict, className, ...props }: MeasurementRowProps): React.JSX.Element;
87
+
88
+ /**
89
+ * 产线主操作按钮:每个工位状态只保留一个主动作。
90
+ * lg = 3rem 高(高频主操作最小推荐),xl = 4.5rem(超大工位/厚手套)。
91
+ * 默认全宽;全部 rem 尺寸,随根字号(校准基准 × 模式系数)缩放。
92
+ */
93
+ declare const primaryActionVariants: (props?: ({
94
+ size?: "lg" | "xl" | null | undefined;
95
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
96
+ interface PrimaryActionProps extends React.ComponentProps<"button">, VariantProps<typeof primaryActionVariants> {
97
+ /** 危险/终止类动作(红色)。 */
98
+ destructive?: boolean;
99
+ }
100
+ declare function PrimaryAction({ className, size, destructive, ...props }: PrimaryActionProps): React.JSX.Element;
101
+
102
+ /**
103
+ * 条码扫描输入:产线流程的起点。
104
+ * - 2.75rem 最小触摸高度(校准后 ≈11.6mm 物理高度);
105
+ * - Enter 提交后全选,扫码枪连续扫码无需手动清空;
106
+ * - mono 字体 + 大字号,SN 逐字符可核对。
107
+ */
108
+ interface ScanInputProps extends Omit<React.ComponentProps<"input">, "onSubmit" | "onChange" | "value"> {
109
+ label?: string;
110
+ value: string;
111
+ onChange: (value: string) => void;
112
+ /** Enter(扫码枪回车后缀)触发;空值不触发。 */
113
+ onSubmit: (value: string) => void;
114
+ }
115
+ declare function ScanInput({ label, value, onChange, onSubmit, className, disabled, ...props }: ScanInputProps): React.JSX.Element;
116
+
117
+ export { Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, MeasurementRow, type MeasurementRowProps, type MeasurementVerdict, PrimaryAction, type PrimaryActionProps, ScanInput, type ScanInputProps, type StationState, StationStatePanel, type StationStatePanelProps, type StepDef, type StepRunResult, StepRunner, badgeVariants, buttonVariants, cn, primaryActionVariants, panelVariants as stationStatePanelVariants };
package/dist/index.js ADDED
@@ -0,0 +1,439 @@
1
+ // src/lib/utils.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ // src/components/button.tsx
9
+ import { Slot } from "@radix-ui/react-slot";
10
+ import { cva } from "class-variance-authority";
11
+ import { jsx } from "react/jsx-runtime";
12
+ var buttonVariants = cva(
13
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ {
15
+ variants: {
16
+ variant: {
17
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
18
+ destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
19
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
20
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
21
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
+ link: "text-primary underline-offset-4 hover:underline"
23
+ },
24
+ size: {
25
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
26
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
+ icon: "size-9"
29
+ }
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default"
34
+ }
35
+ }
36
+ );
37
+ function Button({
38
+ className,
39
+ variant,
40
+ size,
41
+ asChild = false,
42
+ ...props
43
+ }) {
44
+ const Comp = asChild ? Slot : "button";
45
+ return /* @__PURE__ */ jsx(
46
+ Comp,
47
+ {
48
+ "data-slot": "button",
49
+ className: cn(buttonVariants({ variant, size, className })),
50
+ ...props
51
+ }
52
+ );
53
+ }
54
+
55
+ // src/components/card.tsx
56
+ import { jsx as jsx2 } from "react/jsx-runtime";
57
+ function Card({ className, ...props }) {
58
+ return /* @__PURE__ */ jsx2(
59
+ "div",
60
+ {
61
+ "data-slot": "card",
62
+ className: cn(
63
+ "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
64
+ className
65
+ ),
66
+ ...props
67
+ }
68
+ );
69
+ }
70
+ function CardHeader({ className, ...props }) {
71
+ return /* @__PURE__ */ jsx2(
72
+ "div",
73
+ {
74
+ "data-slot": "card-header",
75
+ className: cn(
76
+ "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
77
+ className
78
+ ),
79
+ ...props
80
+ }
81
+ );
82
+ }
83
+ function CardTitle({ className, ...props }) {
84
+ return /* @__PURE__ */ jsx2(
85
+ "div",
86
+ {
87
+ "data-slot": "card-title",
88
+ className: cn("leading-none font-semibold", className),
89
+ ...props
90
+ }
91
+ );
92
+ }
93
+ function CardDescription({ className, ...props }) {
94
+ return /* @__PURE__ */ jsx2(
95
+ "div",
96
+ {
97
+ "data-slot": "card-description",
98
+ className: cn("text-muted-foreground text-sm", className),
99
+ ...props
100
+ }
101
+ );
102
+ }
103
+ function CardContent({ className, ...props }) {
104
+ return /* @__PURE__ */ jsx2("div", { "data-slot": "card-content", className: cn("px-6", className), ...props });
105
+ }
106
+ function CardFooter({ className, ...props }) {
107
+ return /* @__PURE__ */ jsx2(
108
+ "div",
109
+ {
110
+ "data-slot": "card-footer",
111
+ className: cn("flex items-center px-6 [.border-t]:pt-6", className),
112
+ ...props
113
+ }
114
+ );
115
+ }
116
+
117
+ // src/components/badge.tsx
118
+ import { Slot as Slot2 } from "@radix-ui/react-slot";
119
+ import { cva as cva2 } from "class-variance-authority";
120
+ import { jsx as jsx3 } from "react/jsx-runtime";
121
+ var badgeVariants = cva2(
122
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
123
+ {
124
+ variants: {
125
+ variant: {
126
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
127
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
128
+ destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
129
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
130
+ success: "border-transparent bg-emerald-600 text-white"
131
+ }
132
+ },
133
+ defaultVariants: {
134
+ variant: "default"
135
+ }
136
+ }
137
+ );
138
+ function Badge({
139
+ className,
140
+ variant,
141
+ asChild = false,
142
+ ...props
143
+ }) {
144
+ const Comp = asChild ? Slot2 : "span";
145
+ return /* @__PURE__ */ jsx3(Comp, { "data-slot": "badge", className: cn(badgeVariants({ variant }), className), ...props });
146
+ }
147
+
148
+ // src/components/step-runner.tsx
149
+ import * as React from "react";
150
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
151
+ function StepRunner({
152
+ steps,
153
+ emptyHint
154
+ }) {
155
+ const [records, setRecords] = React.useState([]);
156
+ const [busy, setBusy] = React.useState(null);
157
+ const onRun = async (step) => {
158
+ setBusy(step.key);
159
+ try {
160
+ const r = await step.run();
161
+ setRecords((prev) => [{ ...r, ts: (/* @__PURE__ */ new Date()).toISOString() }, ...prev].slice(0, 50));
162
+ } catch (e) {
163
+ setRecords(
164
+ (prev) => [
165
+ {
166
+ ok: false,
167
+ stepKey: step.key,
168
+ error: e instanceof Error ? e.message : String(e),
169
+ ts: (/* @__PURE__ */ new Date()).toISOString()
170
+ },
171
+ ...prev
172
+ ].slice(0, 50)
173
+ );
174
+ } finally {
175
+ setBusy(null);
176
+ }
177
+ };
178
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
179
+ /* @__PURE__ */ jsx4("div", { className: "flex flex-wrap gap-2", children: steps.map((s) => /* @__PURE__ */ jsx4(
180
+ Button,
181
+ {
182
+ variant: "secondary",
183
+ size: "sm",
184
+ disabled: busy !== null,
185
+ title: s.description,
186
+ onClick: () => void onRun(s),
187
+ children: busy === s.key ? "\u8FD0\u884C\u4E2D\u2026" : s.label
188
+ },
189
+ s.key
190
+ )) }),
191
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
192
+ records.length === 0 && /* @__PURE__ */ jsx4("div", { className: "rounded-md bg-muted/30 px-2 py-3 text-center text-xs text-muted-foreground", children: emptyHint ?? "\u70B9\u51FB\u4E0A\u65B9\u6309\u94AE\u6267\u884C\u6D4B\u8BD5\u6B65\u9AA4\u3002" }),
193
+ records.map((r, i) => /* @__PURE__ */ jsxs(
194
+ "div",
195
+ {
196
+ className: cn(
197
+ "rounded-md border p-2 text-xs",
198
+ r.ok ? "border-emerald-500/30 bg-emerald-500/5" : "border-destructive/30 bg-destructive/5"
199
+ ),
200
+ children: [
201
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
202
+ /* @__PURE__ */ jsx4(
203
+ "span",
204
+ {
205
+ className: cn(
206
+ "rounded px-1.5 py-0.5 text-[10px] font-semibold",
207
+ r.ok ? "bg-emerald-500/20 text-emerald-400" : "bg-destructive/20 text-destructive"
208
+ ),
209
+ children: r.ok ? "PASS" : "FAIL"
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx4("span", { className: "font-medium", children: r.stepKey }),
213
+ /* @__PURE__ */ jsx4("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: r.ts.slice(11, 19) })
214
+ ] }),
215
+ r.measurements && Object.keys(r.measurements).length > 0 && /* @__PURE__ */ jsx4("pre", { className: "mt-1.5 overflow-x-auto rounded bg-muted/50 p-1.5 font-mono text-[10px]", children: JSON.stringify(r.measurements) }),
216
+ r.error && /* @__PURE__ */ jsx4("div", { className: "mt-1 text-destructive", children: r.error })
217
+ ]
218
+ },
219
+ i
220
+ ))
221
+ ] })
222
+ ] });
223
+ }
224
+
225
+ // src/components/station-state-panel.tsx
226
+ import { cva as cva3 } from "class-variance-authority";
227
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
228
+ var STATE_META = {
229
+ idle: { word: "\u5F85\u673A", hint: "\u7B49\u5F85\u626B\u7801" },
230
+ running: { word: "\u6D4B\u8BD5\u4E2D", hint: "\u8BF7\u52FF\u79FB\u52A8\u4EA7\u54C1" },
231
+ pass: { word: "\u2713 PASS", hint: "\u5408\u683C" },
232
+ fail: { word: "\u2715 FAIL", hint: "\u4E0D\u5408\u683C" },
233
+ fault: { word: "\u26A0 FAULT", hint: "\u5DE5\u4F4D\u6545\u969C" }
234
+ };
235
+ var panelVariants = cva3(
236
+ "flex min-h-[12rem] w-full flex-col items-center justify-center gap-[0.5rem] rounded-lg border-2 px-[1.5rem] py-[2rem] text-center",
237
+ {
238
+ variants: {
239
+ state: {
240
+ idle: "border-border bg-card text-muted-foreground",
241
+ running: "border-sky-500/60 bg-sky-500/10 text-sky-400",
242
+ pass: "border-emerald-500/60 bg-emerald-500/10 text-emerald-400",
243
+ fail: "border-red-500/60 bg-red-500/10 text-red-400",
244
+ fault: "border-amber-500/60 bg-amber-500/10 text-amber-400"
245
+ }
246
+ }
247
+ }
248
+ );
249
+ function StationStatePanel({
250
+ state,
251
+ title,
252
+ detail,
253
+ className,
254
+ ...props
255
+ }) {
256
+ const meta = STATE_META[state];
257
+ return /* @__PURE__ */ jsxs2(
258
+ "div",
259
+ {
260
+ "data-slot": "station-state-panel",
261
+ role: "status",
262
+ "aria-live": "polite",
263
+ className: cn(panelVariants({ state }), className),
264
+ ...props,
265
+ children: [
266
+ /* @__PURE__ */ jsx5(
267
+ "span",
268
+ {
269
+ className: cn(
270
+ "text-[3.75rem] leading-none font-bold tracking-tight",
271
+ state === "running" && "animate-pulse"
272
+ ),
273
+ children: title ?? meta.word
274
+ }
275
+ ),
276
+ /* @__PURE__ */ jsx5("span", { className: "text-[1.125rem] font-medium opacity-80", children: detail ?? meta.hint })
277
+ ]
278
+ }
279
+ );
280
+ }
281
+
282
+ // src/components/measurement-row.tsx
283
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
284
+ var VERDICT_META = {
285
+ pass: { text: "\u2713 PASS", className: "border-emerald-500/50 bg-emerald-500/10 text-emerald-400" },
286
+ fail: { text: "\u2715 FAIL", className: "border-red-500/50 bg-red-500/10 text-red-400" },
287
+ pending: { text: "\u2026", className: "border-border bg-secondary text-muted-foreground" }
288
+ };
289
+ function formatSpec(lo, hi, unit) {
290
+ if (lo === void 0 && hi === void 0) return "";
291
+ const u = unit ? ` ${unit}` : "";
292
+ if (lo !== void 0 && hi !== void 0) return `\u89C4\u683C\uFF1A${lo} ~ ${hi}${u}`;
293
+ if (lo !== void 0) return `\u89C4\u683C\uFF1A\u2265 ${lo}${u}`;
294
+ return `\u89C4\u683C\uFF1A\u2264 ${hi}${u}`;
295
+ }
296
+ function MeasurementRow({
297
+ label,
298
+ value,
299
+ unit,
300
+ lo,
301
+ hi,
302
+ verdict = "pending",
303
+ className,
304
+ ...props
305
+ }) {
306
+ const meta = VERDICT_META[verdict];
307
+ const spec = formatSpec(lo, hi, unit);
308
+ return /* @__PURE__ */ jsxs3(
309
+ "div",
310
+ {
311
+ "data-slot": "measurement-row",
312
+ className: cn(
313
+ "flex min-h-[3.5rem] w-full items-center gap-[1rem] rounded-md border bg-card px-[1rem] py-[0.5rem]",
314
+ verdict === "fail" && "border-red-500/50",
315
+ className
316
+ ),
317
+ ...props,
318
+ children: [
319
+ /* @__PURE__ */ jsx6("span", { className: "min-w-[6rem] shrink-0 text-[1rem] font-medium", children: label }),
320
+ /* @__PURE__ */ jsxs3("span", { className: "font-mono text-[1.5rem] font-semibold tabular-nums", children: [
321
+ value ?? "--",
322
+ unit ? /* @__PURE__ */ jsx6("span", { className: "ml-[0.375rem] text-[1rem] font-normal text-muted-foreground", children: unit }) : null
323
+ ] }),
324
+ spec ? /* @__PURE__ */ jsx6("span", { className: "ml-auto hidden shrink-0 font-mono text-[0.875rem] text-muted-foreground tabular-nums sm:inline", children: spec }) : null,
325
+ /* @__PURE__ */ jsx6(
326
+ "span",
327
+ {
328
+ className: cn(
329
+ "inline-flex min-w-[5rem] shrink-0 items-center justify-center rounded-md border px-[0.625rem] py-[0.375rem] text-[0.9375rem] font-semibold",
330
+ !spec && "ml-auto",
331
+ meta.className
332
+ ),
333
+ children: meta.text
334
+ }
335
+ )
336
+ ]
337
+ }
338
+ );
339
+ }
340
+
341
+ // src/components/primary-action.tsx
342
+ import { cva as cva4 } from "class-variance-authority";
343
+ import { jsx as jsx7 } from "react/jsx-runtime";
344
+ var primaryActionVariants = cva4("w-full font-semibold", {
345
+ variants: {
346
+ size: {
347
+ lg: "h-[3rem] rounded-lg px-[1.5rem] text-[1.25rem]",
348
+ xl: "h-[4.5rem] rounded-lg px-[2rem] text-[1.5rem]"
349
+ }
350
+ },
351
+ defaultVariants: {
352
+ size: "lg"
353
+ }
354
+ });
355
+ function PrimaryAction({
356
+ className,
357
+ size,
358
+ destructive = false,
359
+ ...props
360
+ }) {
361
+ return /* @__PURE__ */ jsx7(
362
+ "button",
363
+ {
364
+ "data-slot": "primary-action",
365
+ className: cn(
366
+ buttonVariants({ variant: destructive ? "destructive" : "default" }),
367
+ primaryActionVariants({ size }),
368
+ className
369
+ ),
370
+ ...props
371
+ }
372
+ );
373
+ }
374
+
375
+ // src/components/scan-input.tsx
376
+ import * as React2 from "react";
377
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
378
+ function ScanInput({
379
+ label,
380
+ value,
381
+ onChange,
382
+ onSubmit,
383
+ className,
384
+ disabled,
385
+ ...props
386
+ }) {
387
+ const inputRef = React2.useRef(null);
388
+ const handleKeyDown = (e) => {
389
+ if (e.key !== "Enter") return;
390
+ const trimmed = value.trim();
391
+ if (trimmed === "") return;
392
+ onSubmit(trimmed);
393
+ inputRef.current?.select();
394
+ };
395
+ return /* @__PURE__ */ jsxs4("label", { className: "flex w-full flex-col gap-[0.375rem]", children: [
396
+ label ? /* @__PURE__ */ jsx8("span", { className: "text-[0.875rem] font-medium text-muted-foreground", children: label }) : null,
397
+ /* @__PURE__ */ jsx8(
398
+ "input",
399
+ {
400
+ ref: inputRef,
401
+ "data-slot": "scan-input",
402
+ type: "text",
403
+ autoComplete: "off",
404
+ spellCheck: false,
405
+ value,
406
+ disabled,
407
+ onChange: (e) => onChange(e.target.value),
408
+ onKeyDown: handleKeyDown,
409
+ className: cn(
410
+ "h-[2.75rem] w-full rounded-md border bg-background px-[0.875rem] font-mono text-[1.125rem] tracking-wide",
411
+ "outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50",
412
+ "disabled:pointer-events-none disabled:opacity-50",
413
+ className
414
+ ),
415
+ ...props
416
+ }
417
+ )
418
+ ] });
419
+ }
420
+ export {
421
+ Badge,
422
+ Button,
423
+ Card,
424
+ CardContent,
425
+ CardDescription,
426
+ CardFooter,
427
+ CardHeader,
428
+ CardTitle,
429
+ MeasurementRow,
430
+ PrimaryAction,
431
+ ScanInput,
432
+ StationStatePanel,
433
+ StepRunner,
434
+ badgeVariants,
435
+ buttonVariants,
436
+ cn,
437
+ primaryActionVariants,
438
+ panelVariants as stationStatePanelVariants
439
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@international-iot-association/ui",
3
+ "version": "1.0.0-rc.1",
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/International-IoT-Association/MES.git",
12
+ "directory": "electron-station-plugins/packages/ui"
13
+ },
14
+ "description": "Shared shadcn/ui (new-york, zinc) components + dark theme for station plugin UIs.",
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ },
25
+ "./styles.css": "./src/styles.css"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "src/styles.css"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "typecheck": "tsc --noEmit",
34
+ "clean": "rimraf dist .turbo"
35
+ },
36
+ "dependencies": {
37
+ "@radix-ui/react-slot": "^1.3.0",
38
+ "class-variance-authority": "^0.7.1",
39
+ "clsx": "^2.1.1",
40
+ "tailwind-merge": "^3.6.0"
41
+ },
42
+ "peerDependencies": {
43
+ "react": ">=18",
44
+ "react-dom": ">=18"
45
+ },
46
+ "devDependencies": {
47
+ "@international-iot-association/tsconfig": "1.0.0-rc.1",
48
+ "@types/react": "^19.2.7",
49
+ "@types/react-dom": "^19.2.3",
50
+ "react": "^19.2.1",
51
+ "react-dom": "^19.2.1",
52
+ "tsup": "^8.4.0",
53
+ "typescript": "^5.9.3"
54
+ },
55
+ "x-internal-version": "1.0.0",
56
+ "x-source-revision": "410a2f3b85ef03b678f04f22ea0a47718f5ddae1"
57
+ }
package/src/styles.css ADDED
@@ -0,0 +1,142 @@
1
+ /*
2
+ @international-iot-association/ui 共享主题 —— shadcn/ui “zinc”(Tailwind v4 / OKLCH),与工位外壳完全一致,
3
+ 让插件 UI 与宿主观感统一。在插件的 CSS 入口中引入:
4
+
5
+ @import "tailwindcss";
6
+ @import "@international-iot-association/ui/styles.css";
7
+ @source "../../node_modules/@international-iot-association/ui/dist"; (扫描共享组件用到的类)
8
+
9
+ 插件 UI 运行在带 <html class="dark"> 的沙箱 iframe 中。
10
+ */
11
+
12
+ @custom-variant dark (&:is(.dark *));
13
+
14
+ :root {
15
+ --radius: 0.625rem;
16
+ --background: oklch(1 0 0);
17
+ --foreground: oklch(0.141 0.005 285.823);
18
+ --card: oklch(1 0 0);
19
+ --card-foreground: oklch(0.141 0.005 285.823);
20
+ --popover: oklch(1 0 0);
21
+ --popover-foreground: oklch(0.141 0.005 285.823);
22
+ --primary: oklch(0.21 0.006 285.885);
23
+ --primary-foreground: oklch(0.985 0 0);
24
+ --secondary: oklch(0.967 0.001 286.375);
25
+ --secondary-foreground: oklch(0.21 0.006 285.885);
26
+ --muted: oklch(0.967 0.001 286.375);
27
+ --muted-foreground: oklch(0.552 0.016 285.938);
28
+ --accent: oklch(0.967 0.001 286.375);
29
+ --accent-foreground: oklch(0.21 0.006 285.885);
30
+ --destructive: oklch(0.577 0.245 27.325);
31
+ --border: oklch(0.92 0.004 286.32);
32
+ --input: oklch(0.92 0.004 286.32);
33
+ --ring: oklch(0.705 0.015 286.067);
34
+ --sidebar: oklch(0.985 0 0);
35
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
36
+ --sidebar-primary: oklch(0.21 0.006 285.885);
37
+ --sidebar-primary-foreground: oklch(0.985 0 0);
38
+ --sidebar-accent: oklch(0.967 0.001 286.375);
39
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
40
+ --sidebar-border: oklch(0.92 0.004 286.32);
41
+ --sidebar-ring: oklch(0.705 0.015 286.067);
42
+ --scrollbar-size: 10px;
43
+ --scrollbar-thumb: oklch(0.552 0.016 285.938 / 32%);
44
+ --scrollbar-thumb-hover: oklch(0.552 0.016 285.938 / 56%);
45
+ }
46
+
47
+ .dark {
48
+ --background: oklch(0.141 0.005 285.823);
49
+ --foreground: oklch(0.985 0 0);
50
+ --card: oklch(0.21 0.006 285.885);
51
+ --card-foreground: oklch(0.985 0 0);
52
+ --popover: oklch(0.21 0.006 285.885);
53
+ --popover-foreground: oklch(0.985 0 0);
54
+ --primary: oklch(0.92 0.004 286.32);
55
+ --primary-foreground: oklch(0.21 0.006 285.885);
56
+ --secondary: oklch(0.274 0.006 286.033);
57
+ --secondary-foreground: oklch(0.985 0 0);
58
+ --muted: oklch(0.274 0.006 286.033);
59
+ --muted-foreground: oklch(0.705 0.015 286.067);
60
+ --accent: oklch(0.274 0.006 286.033);
61
+ --accent-foreground: oklch(0.985 0 0);
62
+ --destructive: oklch(0.704 0.191 22.216);
63
+ --border: oklch(1 0 0 / 10%);
64
+ --input: oklch(1 0 0 / 15%);
65
+ --ring: oklch(0.552 0.016 285.938);
66
+ --sidebar: oklch(0.21 0.006 285.885);
67
+ --sidebar-foreground: oklch(0.985 0 0);
68
+ --sidebar-primary: oklch(0.488 0.243 264.376);
69
+ --sidebar-primary-foreground: oklch(0.985 0 0);
70
+ --sidebar-accent: oklch(0.274 0.006 286.033);
71
+ --sidebar-accent-foreground: oklch(0.985 0 0);
72
+ --sidebar-border: oklch(1 0 0 / 10%);
73
+ --sidebar-ring: oklch(0.552 0.016 285.938);
74
+ --scrollbar-thumb: oklch(0.705 0.015 286.067 / 32%);
75
+ --scrollbar-thumb-hover: oklch(0.705 0.015 286.067 / 58%);
76
+ }
77
+
78
+ @theme inline {
79
+ --radius-sm: calc(var(--radius) - 4px);
80
+ --radius-md: calc(var(--radius) - 2px);
81
+ --radius-lg: var(--radius);
82
+ --radius-xl: calc(var(--radius) + 4px);
83
+ --color-background: var(--background);
84
+ --color-foreground: var(--foreground);
85
+ --color-card: var(--card);
86
+ --color-card-foreground: var(--card-foreground);
87
+ --color-popover: var(--popover);
88
+ --color-popover-foreground: var(--popover-foreground);
89
+ --color-primary: var(--primary);
90
+ --color-primary-foreground: var(--primary-foreground);
91
+ --color-secondary: var(--secondary);
92
+ --color-secondary-foreground: var(--secondary-foreground);
93
+ --color-muted: var(--muted);
94
+ --color-muted-foreground: var(--muted-foreground);
95
+ --color-accent: var(--accent);
96
+ --color-accent-foreground: var(--accent-foreground);
97
+ --color-destructive: var(--destructive);
98
+ --color-border: var(--border);
99
+ --color-input: var(--input);
100
+ --color-ring: var(--ring);
101
+ }
102
+
103
+ @layer base {
104
+ * {
105
+ @apply border-border outline-ring/50;
106
+ scrollbar-color: var(--scrollbar-thumb) transparent;
107
+ scrollbar-width: thin;
108
+ }
109
+ html,
110
+ body,
111
+ #root {
112
+ height: 100%;
113
+ margin: 0;
114
+ }
115
+ body {
116
+ @apply bg-background text-foreground;
117
+ font-family: -apple-system, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
118
+ font-size: 13px;
119
+ -webkit-font-smoothing: antialiased;
120
+ }
121
+ *::-webkit-scrollbar {
122
+ width: var(--scrollbar-size);
123
+ height: var(--scrollbar-size);
124
+ }
125
+ *::-webkit-scrollbar-track {
126
+ background: transparent;
127
+ }
128
+ *::-webkit-scrollbar-thumb {
129
+ min-height: 2rem;
130
+ border: 3px solid transparent;
131
+ border-radius: 999px;
132
+ background: var(--scrollbar-thumb);
133
+ background-clip: content-box;
134
+ }
135
+ *::-webkit-scrollbar-thumb:hover {
136
+ background: var(--scrollbar-thumb-hover);
137
+ background-clip: content-box;
138
+ }
139
+ *::-webkit-scrollbar-corner {
140
+ background: transparent;
141
+ }
142
+ }