@lcars-gui/federation 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,506 @@
1
+ "use client";
2
+
3
+ // src/react/Button.tsx
4
+ import { forwardRef } from "react";
5
+
6
+ // src/shared.ts
7
+ function colorAttribute(color) {
8
+ return color ? { "data-color": String(color) } : {};
9
+ }
10
+
11
+ // src/button.ts
12
+ var defaultButtonProps = {
13
+ color: 1,
14
+ size: "m",
15
+ shape: "capsule",
16
+ align: "center",
17
+ disabled: false
18
+ };
19
+ var buttonClassName = "lcar-btn-std";
20
+ function normalizeButtonProps(props) {
21
+ return {
22
+ ...defaultButtonProps,
23
+ ...props
24
+ };
25
+ }
26
+ function buttonDataAttributes(props) {
27
+ const { color, size, length, shape, align } = normalizeButtonProps(props);
28
+ return {
29
+ "data-color": String(color),
30
+ "data-size": size,
31
+ ...length && { "data-length": length },
32
+ ...shape !== "capsule" && { "data-shape": shape },
33
+ ...align === "bottom-right" && { "data-align": "br" }
34
+ };
35
+ }
36
+
37
+ // src/frame.ts
38
+ var frameClassNames = {
39
+ elbow: "lcar-elbow",
40
+ bar: "lcar-bar",
41
+ block: "lcar-block",
42
+ title: "lcar-title",
43
+ stack: "lcar-stack",
44
+ frame: "lcar-frame",
45
+ frameTop: "lcar-frame-top",
46
+ frameBottom: "lcar-frame-bottom",
47
+ frameContent: "lcar-frame-content",
48
+ frameHeader: "lcar-frame-header"
49
+ };
50
+ function elbowDataAttributes({ corner = "tl", color }) {
51
+ return { "data-corner": corner, ...colorAttribute(color) };
52
+ }
53
+ function barDataAttributes({ color, cap, length }) {
54
+ return {
55
+ ...colorAttribute(color),
56
+ ...cap && { "data-cap": cap },
57
+ ...length && { "data-length": length }
58
+ };
59
+ }
60
+ function blockDataAttributes({ color, fill }) {
61
+ return {
62
+ ...colorAttribute(color),
63
+ ...fill && { "data-fill": "" }
64
+ };
65
+ }
66
+
67
+ // src/display.ts
68
+ import { seededRandom } from "@lcars-gui/core";
69
+ var displayClassNames = {
70
+ cascade: "lcar-cascade",
71
+ cascadeCol: "lcar-cascade-col",
72
+ readout: "lcar-readout",
73
+ number: "lcar-number",
74
+ divider: "lcar-divider",
75
+ grid: "lcar-grid",
76
+ level: "lcar-level",
77
+ gauge: "lcar-gauge",
78
+ sensor: "lcar-sensor",
79
+ blip: "lcar-blip",
80
+ scan: "lcar-scan",
81
+ status: "lcar-status",
82
+ alert: "lcar-alert",
83
+ alertTitle: "lcar-alert-title",
84
+ alertText: "lcar-alert-text"
85
+ };
86
+ var controlClassNames = {
87
+ tabs: "lcar-tabs",
88
+ tablist: "lcar-tablist",
89
+ tab: "lcar-tab",
90
+ tabpanel: "lcar-tabpanel",
91
+ field: "lcar-field",
92
+ fieldLabel: "lcar-field-label",
93
+ input: "lcar-input",
94
+ range: "lcar-range",
95
+ keypad: "lcar-keypad",
96
+ keypadDisplay: "lcar-keypad-display"
97
+ };
98
+ function cascadeNumbers(columns, rows, seed = 47) {
99
+ const random = seededRandom(seed);
100
+ return Array.from({ length: columns }, (_, col) => {
101
+ const digits = 2 + col * 3 % 5;
102
+ return Array.from(
103
+ { length: rows },
104
+ () => String(Math.floor(random() * 10 ** digits)).padStart(digits, "0")
105
+ );
106
+ });
107
+ }
108
+ function levelSegments(value, segments = 10, max = 100) {
109
+ const lit = Math.round(Math.min(Math.max(value, 0), max) / max * segments);
110
+ return Array.from({ length: segments }, (_, i) => i < lit);
111
+ }
112
+ function toPercent(value, max = 100) {
113
+ return Math.min(Math.max(value / max * 100, 0), 100);
114
+ }
115
+
116
+ // src/react/Button.tsx
117
+ import { cx } from "@lcars-gui/core";
118
+ import { useLcarsSound } from "@lcars-gui/core/react";
119
+ import { jsx } from "react/jsx-runtime";
120
+ var Button = forwardRef(
121
+ function Button2({ color, size, length, shape, align, sound = "beep", className, type = "button", onClick, ...rest }, ref) {
122
+ const play = useLcarsSound();
123
+ return /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ ref,
127
+ type,
128
+ className: cx(buttonClassName, className),
129
+ ...buttonDataAttributes({ color, size, length, shape, align }),
130
+ onClick: (e) => {
131
+ if (sound) play(sound);
132
+ onClick?.(e);
133
+ },
134
+ ...rest
135
+ }
136
+ );
137
+ }
138
+ );
139
+
140
+ // src/react/Frame.tsx
141
+ import { cx as cx2 } from "@lcars-gui/core";
142
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
143
+ function Elbow({ corner, color, className, ...rest }) {
144
+ return /* @__PURE__ */ jsx2(
145
+ "div",
146
+ {
147
+ className: cx2(frameClassNames.elbow, className),
148
+ ...elbowDataAttributes({ corner, color }),
149
+ ...rest
150
+ }
151
+ );
152
+ }
153
+ function Bar({ color, cap, length, className, ...rest }) {
154
+ return /* @__PURE__ */ jsx2(
155
+ "div",
156
+ {
157
+ className: cx2(frameClassNames.bar, className),
158
+ ...barDataAttributes({ color, cap, length }),
159
+ ...rest
160
+ }
161
+ );
162
+ }
163
+ function Block({ color, fill, className, ...rest }) {
164
+ return /* @__PURE__ */ jsx2(
165
+ "div",
166
+ {
167
+ className: cx2(frameClassNames.block, className),
168
+ ...blockDataAttributes({ color, fill }),
169
+ ...rest
170
+ }
171
+ );
172
+ }
173
+ function Title({ color, className, ...rest }) {
174
+ return /* @__PURE__ */ jsx2(
175
+ "h1",
176
+ {
177
+ className: cx2(frameClassNames.title, className),
178
+ ...colorAttribute(color),
179
+ ...rest
180
+ }
181
+ );
182
+ }
183
+ function Stack({ className, ...rest }) {
184
+ return /* @__PURE__ */ jsx2("div", { className: cx2(frameClassNames.stack, className), ...rest });
185
+ }
186
+ function Frame({
187
+ color,
188
+ title,
189
+ topLabel,
190
+ bottomLabel,
191
+ sidebar,
192
+ top,
193
+ bottom,
194
+ className,
195
+ children,
196
+ ...rest
197
+ }) {
198
+ return /* @__PURE__ */ jsxs("div", { className: cx2(frameClassNames.frame, className), ...rest, children: [
199
+ /* @__PURE__ */ jsxs("div", { className: frameClassNames.frameTop, children: [
200
+ /* @__PURE__ */ jsx2(Elbow, { corner: "tl", color, children: topLabel }),
201
+ top ?? /* @__PURE__ */ jsx2(Bar, { color }),
202
+ title != null && /* @__PURE__ */ jsxs(Fragment, { children: [
203
+ /* @__PURE__ */ jsx2(Title, { color, children: title }),
204
+ /* @__PURE__ */ jsx2(Bar, { color, cap: "right", length: "xs" })
205
+ ] })
206
+ ] }),
207
+ /* @__PURE__ */ jsxs(Stack, { children: [
208
+ sidebar,
209
+ /* @__PURE__ */ jsx2(Block, { color, fill: true })
210
+ ] }),
211
+ /* @__PURE__ */ jsx2("div", { className: frameClassNames.frameContent, children }),
212
+ /* @__PURE__ */ jsxs("div", { className: frameClassNames.frameBottom, children: [
213
+ /* @__PURE__ */ jsx2(Elbow, { corner: "bl", color, children: bottomLabel }),
214
+ bottom ?? /* @__PURE__ */ jsx2(Bar, { color, cap: "right" })
215
+ ] })
216
+ ] });
217
+ }
218
+ function SplitFrame({
219
+ color,
220
+ title,
221
+ header,
222
+ headerSidebar,
223
+ sidebar,
224
+ upperLabel,
225
+ lowerLabel,
226
+ upperBar,
227
+ lowerBar,
228
+ className,
229
+ children,
230
+ ...rest
231
+ }) {
232
+ return /* @__PURE__ */ jsxs("div", { className: cx2(frameClassNames.frame, className), "data-layout": "split", ...rest, children: [
233
+ /* @__PURE__ */ jsxs(Stack, { children: [
234
+ headerSidebar,
235
+ /* @__PURE__ */ jsx2(Block, { color, fill: true })
236
+ ] }),
237
+ /* @__PURE__ */ jsxs("div", { className: cx2(frameClassNames.frameContent, frameClassNames.frameHeader), children: [
238
+ header,
239
+ title != null && /* @__PURE__ */ jsx2(Title, { color, children: title })
240
+ ] }),
241
+ /* @__PURE__ */ jsxs("div", { className: frameClassNames.frameBottom, children: [
242
+ /* @__PURE__ */ jsx2(Elbow, { corner: "bl", color, children: upperLabel }),
243
+ upperBar ?? /* @__PURE__ */ jsx2(Bar, { color, cap: "right" })
244
+ ] }),
245
+ /* @__PURE__ */ jsxs("div", { className: frameClassNames.frameTop, children: [
246
+ /* @__PURE__ */ jsx2(Elbow, { corner: "tl", color, children: lowerLabel }),
247
+ lowerBar ?? /* @__PURE__ */ jsx2(Bar, { color, cap: "right" })
248
+ ] }),
249
+ /* @__PURE__ */ jsxs(Stack, { children: [
250
+ sidebar,
251
+ /* @__PURE__ */ jsx2(Block, { color, fill: true })
252
+ ] }),
253
+ /* @__PURE__ */ jsx2("div", { className: frameClassNames.frameContent, children })
254
+ ] });
255
+ }
256
+
257
+ // src/react/Controls.tsx
258
+ import {
259
+ forwardRef as forwardRef2,
260
+ useId,
261
+ useState
262
+ } from "react";
263
+ import { useLcarsSound as useLcarsSound2 } from "@lcars-gui/core/react";
264
+ import { cx as cx3 } from "@lcars-gui/core";
265
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
266
+ function Toggle({ pressed, defaultPressed = false, onPressedChange, onClick, ...rest }) {
267
+ const [inner, setInner] = useState(defaultPressed);
268
+ const isOn = pressed ?? inner;
269
+ return /* @__PURE__ */ jsx3(
270
+ Button,
271
+ {
272
+ "aria-pressed": isOn,
273
+ onClick: (e) => {
274
+ setInner(!isOn);
275
+ onPressedChange?.(!isOn);
276
+ onClick?.(e);
277
+ },
278
+ ...rest
279
+ }
280
+ );
281
+ }
282
+ var Slider = forwardRef2(function Slider2({ color, className, ...rest }, ref) {
283
+ return /* @__PURE__ */ jsx3(
284
+ "input",
285
+ {
286
+ ref,
287
+ type: "range",
288
+ className: cx3(controlClassNames.range, className),
289
+ ...colorAttribute(color),
290
+ ...rest
291
+ }
292
+ );
293
+ });
294
+ var Input = forwardRef2(function Input2({ color, label, className, ...rest }, ref) {
295
+ return /* @__PURE__ */ jsxs2("label", { className: controlClassNames.field, ...colorAttribute(color), children: [
296
+ label != null && /* @__PURE__ */ jsx3("span", { className: controlClassNames.fieldLabel, children: label }),
297
+ /* @__PURE__ */ jsx3("input", { ref, className: cx3(controlClassNames.input, className), ...rest })
298
+ ] });
299
+ });
300
+ function Tabs({ items, value, defaultValue, onValueChange, color, className, ...rest }) {
301
+ const base = useId();
302
+ const play = useLcarsSound2();
303
+ const [inner, setInner] = useState(defaultValue ?? items[0]?.id);
304
+ const active = value ?? inner;
305
+ const select = (id) => {
306
+ setInner(id);
307
+ onValueChange?.(id);
308
+ play("beep");
309
+ };
310
+ const onKeyDown = (e) => {
311
+ const step = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0;
312
+ if (!step) return;
313
+ const index = items.findIndex((item) => item.id === active);
314
+ const next = items[(index + step + items.length) % items.length];
315
+ select(next.id);
316
+ document.getElementById(`${base}-tab-${next.id}`)?.focus();
317
+ };
318
+ return /* @__PURE__ */ jsxs2("div", { className: cx3(controlClassNames.tabs, className), ...colorAttribute(color), ...rest, children: [
319
+ /* @__PURE__ */ jsx3("div", { role: "tablist", className: controlClassNames.tablist, onKeyDown, children: items.map((item) => {
320
+ const selected = item.id === active;
321
+ return /* @__PURE__ */ jsx3(
322
+ "button",
323
+ {
324
+ type: "button",
325
+ role: "tab",
326
+ id: `${base}-tab-${item.id}`,
327
+ "aria-selected": selected,
328
+ "aria-controls": `${base}-panel-${item.id}`,
329
+ tabIndex: selected ? 0 : -1,
330
+ className: controlClassNames.tab,
331
+ ...colorAttribute(item.color),
332
+ onClick: () => select(item.id),
333
+ children: item.label
334
+ },
335
+ item.id
336
+ );
337
+ }) }),
338
+ /* @__PURE__ */ jsx3(Bar, { cap: "right", color }),
339
+ items.map((item) => /* @__PURE__ */ jsx3(
340
+ "div",
341
+ {
342
+ role: "tabpanel",
343
+ id: `${base}-panel-${item.id}`,
344
+ "aria-labelledby": `${base}-tab-${item.id}`,
345
+ hidden: item.id !== active,
346
+ className: controlClassNames.tabpanel,
347
+ children: item.content
348
+ },
349
+ item.id
350
+ ))
351
+ ] });
352
+ }
353
+ var keys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "CLR", "0", "ENT"];
354
+ function Keypad({ color = 4, length = 6, mask, onSubmit, className, ...rest }) {
355
+ const play = useLcarsSound2();
356
+ const [code, setCode] = useState("");
357
+ const [state, setState] = useState();
358
+ const press = (key) => {
359
+ setState(void 0);
360
+ if (key === "CLR") return setCode("");
361
+ if (key === "ENT") {
362
+ const accepted = onSubmit?.(code);
363
+ if (accepted === void 0) return play("confirm");
364
+ setState(accepted ? "ok" : "error");
365
+ play(accepted ? "confirm" : "deny");
366
+ return setCode("");
367
+ }
368
+ if (code.length < length) setCode(code + key);
369
+ };
370
+ const shown = state === "ok" ? "OK" : state === "error" ? "DENIED" : mask ? "\u2022".repeat(code.length) : code;
371
+ return /* @__PURE__ */ jsxs2("div", { className: cx3(controlClassNames.keypad, className), ...colorAttribute(color), ...rest, children: [
372
+ /* @__PURE__ */ jsx3("output", { className: controlClassNames.keypadDisplay, "data-state": state, children: shown || "\u2013".repeat(length) }),
373
+ keys.map((key) => /* @__PURE__ */ jsx3(
374
+ Button,
375
+ {
376
+ shape: "rect",
377
+ align: "bottom-right",
378
+ color: key === "CLR" ? 2 : key === "ENT" ? 6 : color,
379
+ sound: key === "ENT" ? false : "beep",
380
+ onClick: () => press(key),
381
+ children: key
382
+ },
383
+ key
384
+ ))
385
+ ] });
386
+ }
387
+
388
+ // src/react/Display.tsx
389
+ import { useMemo } from "react";
390
+ import { cx as cx4 } from "@lcars-gui/core";
391
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
392
+ function DataCascade({ columns = 8, rows = 6, seed = 47, className, ...rest }) {
393
+ const data = useMemo(() => cascadeNumbers(columns, rows, seed), [columns, rows, seed]);
394
+ return /* @__PURE__ */ jsx4("div", { className: cx4(displayClassNames.cascade, className), "aria-hidden": true, ...rest, children: data.map((col, c) => /* @__PURE__ */ jsx4("div", { className: displayClassNames.cascadeCol, children: col.map((n, r) => /* @__PURE__ */ jsx4("span", { style: { "--i": r * 2 + c }, children: n }, r)) }, c)) });
395
+ }
396
+ function Readout({ items, color, className, ...rest }) {
397
+ return /* @__PURE__ */ jsx4("dl", { className: cx4(displayClassNames.readout, className), ...colorAttribute(color), ...rest, children: items.map((item, i) => /* @__PURE__ */ jsxs3("div", { style: { display: "contents" }, children: [
398
+ /* @__PURE__ */ jsx4("dt", { ...colorAttribute(item.color), children: item.label }),
399
+ /* @__PURE__ */ jsx4("dd", { children: item.value })
400
+ ] }, i)) });
401
+ }
402
+ function NumberCode({ color, className, ...rest }) {
403
+ return /* @__PURE__ */ jsx4("span", { className: cx4(displayClassNames.number, className), ...colorAttribute(color), ...rest });
404
+ }
405
+ function Divider({ color, className, ...rest }) {
406
+ return /* @__PURE__ */ jsx4("div", { role: "separator", className: cx4(displayClassNames.divider, className), ...colorAttribute(color), ...rest });
407
+ }
408
+ function ButtonGrid({ columns = 4, className, style, ...rest }) {
409
+ return /* @__PURE__ */ jsx4("div", { className: cx4(displayClassNames.grid, className), style: { "--cols": columns, ...style }, ...rest });
410
+ }
411
+ function LevelBar({ value, max = 100, segments = 10, orientation, color, className, ...rest }) {
412
+ return /* @__PURE__ */ jsx4(
413
+ "div",
414
+ {
415
+ role: "meter",
416
+ "aria-valuenow": value,
417
+ "aria-valuemin": 0,
418
+ "aria-valuemax": max,
419
+ className: cx4(displayClassNames.level, className),
420
+ "data-orientation": orientation,
421
+ ...colorAttribute(color),
422
+ ...rest,
423
+ children: levelSegments(value, segments, max).map((on, i) => /* @__PURE__ */ jsx4("span", { "data-on": on ? "" : void 0 }, i))
424
+ }
425
+ );
426
+ }
427
+ function Gauge({ value, max = 100, segments = 12, label, color, className, style, ...rest }) {
428
+ return /* @__PURE__ */ jsxs3(
429
+ "div",
430
+ {
431
+ role: "meter",
432
+ "aria-valuenow": value,
433
+ "aria-valuemin": 0,
434
+ "aria-valuemax": max,
435
+ className: cx4(displayClassNames.gauge, className),
436
+ style: { "--value": toPercent(value, max), "--segments": segments, ...style },
437
+ ...colorAttribute(color),
438
+ ...rest,
439
+ children: [
440
+ /* @__PURE__ */ jsx4("span", { children: Math.round(value) }),
441
+ label != null && /* @__PURE__ */ jsx4("small", { children: label })
442
+ ]
443
+ }
444
+ );
445
+ }
446
+ function Sensor({ blips = [], scan, color, className, children, ...rest }) {
447
+ return /* @__PURE__ */ jsxs3("div", { className: cx4(cx4(displayClassNames.sensor, scan ? displayClassNames.scan : void 0), className), ...colorAttribute(color), ...rest, children: [
448
+ blips.map((b, i) => /* @__PURE__ */ jsx4("span", { className: displayClassNames.blip, style: { "--x": b.x, "--y": b.y }, ...colorAttribute(b.color) }, i)),
449
+ children
450
+ ] });
451
+ }
452
+ function StatusIndicator({ status = "ok", blink, className, ...rest }) {
453
+ return /* @__PURE__ */ jsx4(
454
+ "span",
455
+ {
456
+ role: "status",
457
+ className: cx4(displayClassNames.status, className),
458
+ "data-status": status,
459
+ "data-blink": blink ? "" : void 0,
460
+ ...rest
461
+ }
462
+ );
463
+ }
464
+ function AlertBanner({ level = "red", title = "Alert", children, className, ...rest }) {
465
+ return /* @__PURE__ */ jsxs3("div", { role: "alert", className: cx4(displayClassNames.alert, className), "data-level": level, ...rest, children: [
466
+ /* @__PURE__ */ jsx4("span", { className: displayClassNames.alertTitle, children: title }),
467
+ /* @__PURE__ */ jsx4("span", { className: displayClassNames.alertText, children: children ?? `Condition: ${level}` })
468
+ ] });
469
+ }
470
+
471
+ // src/react/Root.tsx
472
+ import { jsx as jsx5 } from "react/jsx-runtime";
473
+ function LcarsRoot({ theme, alert, ...rest }) {
474
+ return /* @__PURE__ */ jsx5("div", { "data-theme": theme, "data-alert": alert ?? void 0, ...rest });
475
+ }
476
+
477
+ // src/react/index.ts
478
+ import { SoundProvider, useLcarsSound as useLcarsSound3 } from "@lcars-gui/core/react";
479
+ export {
480
+ AlertBanner,
481
+ Bar,
482
+ Block,
483
+ Button,
484
+ ButtonGrid,
485
+ DataCascade,
486
+ Divider,
487
+ Elbow,
488
+ Frame,
489
+ Gauge,
490
+ Input,
491
+ Keypad,
492
+ LcarsRoot,
493
+ LevelBar,
494
+ NumberCode,
495
+ Readout,
496
+ Sensor,
497
+ Slider,
498
+ SoundProvider,
499
+ SplitFrame,
500
+ Stack,
501
+ StatusIndicator,
502
+ Tabs,
503
+ Title,
504
+ Toggle,
505
+ useLcarsSound3 as useLcarsSound
506
+ };
@@ -0,0 +1,5 @@
1
+ /* Theme-Extras: DS9 – Raumstation Deep Space Nine
2
+ Wird als @lcars-gui/federation/themes/ds9.css veröffentlicht.
3
+ Hier kommen zusätzliche Animationen/Effekte hin, die nur zu diesem Theme passen.
4
+ Alle Regeln unter [data-theme="ds9"] einhängen, damit die Datei auch bei
5
+ anderen aktiven Themes gefahrlos geladen werden kann. */
@@ -0,0 +1,5 @@
1
+ /* Theme-Extras: Picard – 25. Jahrhundert
2
+ Wird als @lcars-gui/federation/themes/picard.css veröffentlicht.
3
+ Hier kommen zusätzliche Animationen/Effekte hin, die nur zu diesem Theme passen.
4
+ Alle Regeln unter [data-theme="picard"] einhängen, damit die Datei auch bei
5
+ anderen aktiven Themes gefahrlos geladen werden kann. */
@@ -0,0 +1,5 @@
1
+ /* Theme-Extras: TNG – Enterprise-D
2
+ Wird als @lcars-gui/federation/themes/tng.css veröffentlicht.
3
+ Hier kommen zusätzliche Animationen/Effekte hin, die nur zu diesem Theme passen.
4
+ Alle Regeln unter [data-theme="tng"] einhängen, damit die Datei auch bei
5
+ anderen aktiven Themes gefahrlos geladen werden kann. */