@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 +4 -0
- package/dist/index.cjs +493 -0
- package/dist/index.d.cts +117 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +439 -0
- package/package.json +57 -0
- package/src/styles.css +142 -0
package/README.md
ADDED
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
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|