@neuralumina/lumina-ui 0.1.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.
@@ -0,0 +1,624 @@
1
+ import {
2
+ alignmentStyle,
3
+ cleanStyle,
4
+ decorationStyle,
5
+ edgeInsets,
6
+ flexCrossAlignment,
7
+ flexMainAlignment,
8
+ luminaTheme,
9
+ normalizeWidgetArgs,
10
+ px,
11
+ } from "./utils.js";
12
+
13
+ function layoutProps(props, omitted = []) {
14
+ const omittedSet = new Set([
15
+ "align",
16
+ "alignment",
17
+ "crossAxisAlignment",
18
+ "mainAxisAlignment",
19
+ "justifyContent",
20
+ "gap",
21
+ "padding",
22
+ "margin",
23
+ "width",
24
+ "height",
25
+ "minWidth",
26
+ "minHeight",
27
+ "maxWidth",
28
+ "maxHeight",
29
+ "color",
30
+ "decoration",
31
+ "child",
32
+ "children",
33
+ "style",
34
+ "key",
35
+ ...omitted,
36
+ ]);
37
+
38
+ return Object.fromEntries(
39
+ Object.entries(props).filter(([key]) => !omittedSet.has(key)),
40
+ );
41
+ }
42
+
43
+ function flexLayout(direction, fallbackCross, propsOrChildren, maybeChildren) {
44
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
45
+ const {
46
+ gap = 0,
47
+ padding = 0,
48
+ mainAxisAlignment,
49
+ crossAxisAlignment,
50
+ justifyContent,
51
+ align,
52
+ alignment,
53
+ style = {},
54
+ } = props;
55
+
56
+ const finalStyle = cleanStyle({
57
+ display: "flex",
58
+ flexDirection: direction,
59
+ gap: px(gap),
60
+ padding: edgeInsets(padding),
61
+ alignItems: flexCrossAlignment(
62
+ crossAxisAlignment ?? align ?? alignment,
63
+ fallbackCross,
64
+ ),
65
+ justifyContent: flexMainAlignment(
66
+ mainAxisAlignment ?? justifyContent,
67
+ "flex-start",
68
+ ),
69
+ ...style,
70
+ });
71
+
72
+ return {
73
+ tag: "div",
74
+ props: { ...layoutProps(props), style: finalStyle },
75
+ children,
76
+ key: props.key,
77
+ };
78
+ }
79
+
80
+ export function Column(propsOrChildren = {}, maybeChildren = undefined) {
81
+ return flexLayout("column", "stretch", propsOrChildren, maybeChildren);
82
+ }
83
+
84
+ export function Row(propsOrChildren = {}, maybeChildren = undefined) {
85
+ return flexLayout("row", "center", propsOrChildren, maybeChildren);
86
+ }
87
+
88
+ export function Container(propsOrChildren = {}, maybeChildren = undefined) {
89
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
90
+ const {
91
+ width,
92
+ height,
93
+ minWidth,
94
+ minHeight,
95
+ maxWidth,
96
+ maxHeight,
97
+ color,
98
+ padding,
99
+ margin,
100
+ alignment,
101
+ decoration,
102
+ style = {},
103
+ } = props;
104
+
105
+ const alignmentStyles = alignment
106
+ ? { display: "flex", ...alignmentStyle(alignment) }
107
+ : {};
108
+
109
+ const finalStyle = cleanStyle({
110
+ width: px(width),
111
+ height: px(height),
112
+ minWidth: px(minWidth),
113
+ minHeight: px(minHeight),
114
+ maxWidth: px(maxWidth),
115
+ maxHeight: px(maxHeight),
116
+ backgroundColor: color,
117
+ padding: edgeInsets(padding),
118
+ margin: edgeInsets(margin),
119
+ ...decorationStyle(decoration),
120
+ ...alignmentStyles,
121
+ ...style,
122
+ });
123
+
124
+ return {
125
+ tag: "div",
126
+ props: { ...layoutProps(props), style: finalStyle },
127
+ children,
128
+ key: props.key,
129
+ };
130
+ }
131
+
132
+ export function Center(propsOrChildren = {}, maybeChildren = undefined) {
133
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
134
+ const finalStyle = cleanStyle({
135
+ display: "flex",
136
+ justifyContent: "center",
137
+ alignItems: "center",
138
+ width: props.width === undefined ? "100%" : px(props.width),
139
+ height: props.height === undefined ? "100%" : px(props.height),
140
+ ...props.style,
141
+ });
142
+
143
+ return {
144
+ tag: "div",
145
+ props: { ...layoutProps(props), style: finalStyle },
146
+ children,
147
+ key: props.key,
148
+ };
149
+ }
150
+
151
+ export function Align(propsOrChildren = {}, maybeChildren = undefined) {
152
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
153
+ const finalStyle = cleanStyle({
154
+ display: "flex",
155
+ ...alignmentStyle(props.alignment || "center"),
156
+ width: props.widthFactor ? "fit-content" : px(props.width, "100%"),
157
+ height: props.heightFactor ? "fit-content" : px(props.height, "100%"),
158
+ ...props.style,
159
+ });
160
+
161
+ return {
162
+ tag: "div",
163
+ props: {
164
+ ...layoutProps(props, ["widthFactor", "heightFactor"]),
165
+ style: finalStyle,
166
+ },
167
+ children,
168
+ key: props.key,
169
+ };
170
+ }
171
+
172
+ export function Padding(propsOrChildren = {}, maybeChildren = undefined) {
173
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
174
+ const finalStyle = cleanStyle({
175
+ padding: edgeInsets(props.padding ?? 0),
176
+ ...props.style,
177
+ });
178
+
179
+ return {
180
+ tag: "div",
181
+ props: { ...layoutProps(props), style: finalStyle },
182
+ children,
183
+ key: props.key,
184
+ };
185
+ }
186
+
187
+ export function SizedBox(propsOrChildren = {}, maybeChildren = undefined) {
188
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
189
+ const finalStyle = cleanStyle({
190
+ width: px(props.width),
191
+ height: px(props.height),
192
+ display: children.length ? undefined : "block",
193
+ flexShrink: 0,
194
+ ...props.style,
195
+ });
196
+
197
+ return {
198
+ tag: "div",
199
+ props: { ...layoutProps(props), style: finalStyle },
200
+ children,
201
+ key: props.key,
202
+ };
203
+ }
204
+
205
+ export function Flexible(propsOrChildren = {}, maybeChildren = undefined) {
206
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
207
+ const flex = props.flex ?? 1;
208
+ const fit = props.fit || "loose";
209
+ const finalStyle = cleanStyle({
210
+ flex: `${flex} ${fit === "tight" ? 1 : 0} ${fit === "tight" ? "0%" : "auto"}`,
211
+ minWidth: 0,
212
+ minHeight: 0,
213
+ ...props.style,
214
+ });
215
+
216
+ return {
217
+ tag: "div",
218
+ props: { ...layoutProps(props, ["flex", "fit"]), style: finalStyle },
219
+ children,
220
+ key: props.key,
221
+ };
222
+ }
223
+
224
+ export function Expanded(propsOrChildren = {}, maybeChildren = undefined) {
225
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
226
+ return Flexible({ ...props, fit: "tight" }, children);
227
+ }
228
+
229
+ export function Spacer(props = {}) {
230
+ const flex = props.flex ?? 1;
231
+ return {
232
+ tag: "div",
233
+ props: {
234
+ ...layoutProps(props, ["flex"]),
235
+ "aria-hidden": "true",
236
+ style: cleanStyle({ flex: `${flex} 1 0%`, ...props.style }),
237
+ },
238
+ children: [],
239
+ key: props.key,
240
+ };
241
+ }
242
+
243
+ export function Wrap(propsOrChildren = {}, maybeChildren = undefined) {
244
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
245
+ const finalStyle = cleanStyle({
246
+ display: "flex",
247
+ flexWrap: "wrap",
248
+ flexDirection: props.direction === "vertical" ? "column" : "row",
249
+ gap: px(props.gap ?? props.spacing ?? 0),
250
+ alignItems: flexCrossAlignment(props.crossAxisAlignment, "flex-start"),
251
+ justifyContent: flexMainAlignment(props.alignment, "flex-start"),
252
+ ...props.style,
253
+ });
254
+
255
+ return {
256
+ tag: "div",
257
+ props: {
258
+ ...layoutProps(props, ["direction", "spacing"]),
259
+ style: finalStyle,
260
+ },
261
+ children,
262
+ key: props.key,
263
+ };
264
+ }
265
+
266
+ export function Stack(propsOrChildren = {}, maybeChildren = undefined) {
267
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
268
+ const finalStyle = cleanStyle({
269
+ position: "relative",
270
+ width: px(props.width, "100%"),
271
+ height: px(props.height, "auto"),
272
+ overflow: props.clip === false ? "visible" : "hidden",
273
+ ...props.style,
274
+ });
275
+
276
+ return {
277
+ tag: "div",
278
+ props: { ...layoutProps(props, ["clip"]), style: finalStyle },
279
+ children,
280
+ key: props.key,
281
+ };
282
+ }
283
+
284
+ export function Positioned(propsOrChildren = {}, maybeChildren = undefined) {
285
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
286
+ const finalStyle = cleanStyle({
287
+ position: "absolute",
288
+ top: px(props.top),
289
+ right: px(props.right),
290
+ bottom: px(props.bottom),
291
+ left: px(props.left),
292
+ width: px(props.width),
293
+ height: px(props.height),
294
+ ...props.style,
295
+ });
296
+
297
+ return {
298
+ tag: "div",
299
+ props: {
300
+ ...layoutProps(props, ["top", "right", "bottom", "left"]),
301
+ style: finalStyle,
302
+ },
303
+ children,
304
+ key: props.key,
305
+ };
306
+ }
307
+
308
+ export function Divider(props = {}) {
309
+ const finalStyle = cleanStyle({
310
+ width: props.direction === "vertical" ? px(props.thickness ?? 1) : "100%",
311
+ height: props.direction === "vertical" ? "auto" : px(props.thickness ?? 1),
312
+ minHeight:
313
+ props.direction === "vertical" ? px(props.height, "100%") : undefined,
314
+ border: "none",
315
+ backgroundColor: props.color || luminaTheme.colors.border,
316
+ margin: edgeInsets(
317
+ props.margin ?? (props.direction === "vertical" ? "0 8px" : "8px 0"),
318
+ ),
319
+ flexShrink: 0,
320
+ ...props.style,
321
+ });
322
+
323
+ return {
324
+ tag: "div",
325
+ props: {
326
+ ...layoutProps(props, ["direction", "thickness"]),
327
+ role: props.role || "separator",
328
+ style: finalStyle,
329
+ },
330
+ children: [],
331
+ key: props.key,
332
+ };
333
+ }
334
+
335
+ export function Card(propsOrChildren = {}, maybeChildren = undefined) {
336
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
337
+ return Container(
338
+ {
339
+ ...props,
340
+ decoration: {
341
+ color: luminaTheme.colors.surfaceRaised,
342
+ border: `1px solid ${luminaTheme.colors.border}`,
343
+ borderRadius: props.radius ?? 12,
344
+ boxShadow: props.elevation
345
+ ? `0 ${props.elevation * 3}px ${props.elevation * 8}px rgba(15, 23, 42, 0.12)`
346
+ : luminaTheme.shadow.xs,
347
+ ...(props.decoration || {}),
348
+ },
349
+ padding: props.padding ?? 16,
350
+ style: {
351
+ transition: `box-shadow ${luminaTheme.transition}, border-color ${luminaTheme.transition}, transform ${luminaTheme.transition}`,
352
+ ...props.style,
353
+ },
354
+ },
355
+ children,
356
+ );
357
+ }
358
+
359
+ export function AspectRatio(propsOrChildren = {}, maybeChildren = undefined) {
360
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
361
+ const ratio = props.aspectRatio ?? props.ratio ?? 1;
362
+
363
+ return {
364
+ tag: "div",
365
+ props: {
366
+ ...layoutProps(props, ["aspectRatio", "ratio"]),
367
+ style: cleanStyle({
368
+ aspectRatio: String(ratio),
369
+ width: px(props.width, "100%"),
370
+ ...props.style,
371
+ }),
372
+ },
373
+ children,
374
+ key: props.key,
375
+ };
376
+ }
377
+
378
+ export function Baseline(propsOrChildren = {}, maybeChildren = undefined) {
379
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
380
+
381
+ return {
382
+ tag: "div",
383
+ props: {
384
+ ...layoutProps(props, ["baseline", "baselineType"]),
385
+ style: cleanStyle({
386
+ display: "inline-flex",
387
+ alignItems: "baseline",
388
+ lineHeight: px(props.baseline) || undefined,
389
+ verticalAlign: props.baselineType || "baseline",
390
+ ...props.style,
391
+ }),
392
+ },
393
+ children,
394
+ key: props.key,
395
+ };
396
+ }
397
+
398
+ export function ConstrainedBox(
399
+ propsOrChildren = {},
400
+ maybeChildren = undefined,
401
+ ) {
402
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
403
+ const constraints = props.constraints || {};
404
+
405
+ return {
406
+ tag: "div",
407
+ props: {
408
+ ...layoutProps(props, ["constraints"]),
409
+ style: cleanStyle({
410
+ minWidth: px(props.minWidth ?? constraints.minWidth),
411
+ minHeight: px(props.minHeight ?? constraints.minHeight),
412
+ maxWidth: px(props.maxWidth ?? constraints.maxWidth),
413
+ maxHeight: px(props.maxHeight ?? constraints.maxHeight),
414
+ ...props.style,
415
+ }),
416
+ },
417
+ children,
418
+ key: props.key,
419
+ };
420
+ }
421
+
422
+ export function DecoratedBox(propsOrChildren = {}, maybeChildren = undefined) {
423
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
424
+
425
+ return {
426
+ tag: "div",
427
+ props: {
428
+ ...layoutProps(props, ["position"]),
429
+ style: cleanStyle({
430
+ ...decorationStyle(props.decoration),
431
+ ...props.style,
432
+ }),
433
+ },
434
+ children,
435
+ key: props.key,
436
+ };
437
+ }
438
+
439
+ export function FractionallySizedBox(
440
+ propsOrChildren = {},
441
+ maybeChildren = undefined,
442
+ ) {
443
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
444
+ const align = alignmentStyle(props.alignment || "center");
445
+
446
+ return {
447
+ tag: "div",
448
+ props: {
449
+ ...layoutProps(props, ["widthFactor", "heightFactor"]),
450
+ style: cleanStyle({
451
+ display: "flex",
452
+ ...align,
453
+ width:
454
+ props.widthFactor === undefined
455
+ ? undefined
456
+ : `${props.widthFactor * 100}%`,
457
+ height:
458
+ props.heightFactor === undefined
459
+ ? undefined
460
+ : `${props.heightFactor * 100}%`,
461
+ ...props.style,
462
+ }),
463
+ },
464
+ children,
465
+ key: props.key,
466
+ };
467
+ }
468
+
469
+ export function LayoutBuilder(props = {}) {
470
+ const constraints = props.constraints || {};
471
+ const child =
472
+ typeof props.builder === "function"
473
+ ? props.builder(constraints)
474
+ : props.child;
475
+
476
+ return {
477
+ tag: "div",
478
+ props: {
479
+ ...layoutProps(props, ["builder", "constraints"]),
480
+ style: cleanStyle({
481
+ width: "100%",
482
+ ...props.style,
483
+ }),
484
+ },
485
+ children: child === undefined ? [] : [child],
486
+ key: props.key,
487
+ };
488
+ }
489
+
490
+ export function LimitedBox(propsOrChildren = {}, maybeChildren = undefined) {
491
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
492
+
493
+ return {
494
+ tag: "div",
495
+ props: {
496
+ ...layoutProps(props),
497
+ style: cleanStyle({
498
+ maxWidth: px(props.maxWidth),
499
+ maxHeight: px(props.maxHeight),
500
+ ...props.style,
501
+ }),
502
+ },
503
+ children,
504
+ key: props.key,
505
+ };
506
+ }
507
+
508
+ export function Offstage(propsOrChildren = {}, maybeChildren = undefined) {
509
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
510
+
511
+ return {
512
+ tag: "div",
513
+ props: {
514
+ ...layoutProps(props, ["offstage"]),
515
+ "aria-hidden": props.offstage ? "true" : undefined,
516
+ style: cleanStyle({
517
+ display: props.offstage ? "none" : undefined,
518
+ ...props.style,
519
+ }),
520
+ },
521
+ children,
522
+ key: props.key,
523
+ };
524
+ }
525
+
526
+ export function OverflowBox(propsOrChildren = {}, maybeChildren = undefined) {
527
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
528
+
529
+ return {
530
+ tag: "div",
531
+ props: {
532
+ ...layoutProps(props),
533
+ style: cleanStyle({
534
+ overflow: "visible",
535
+ minWidth: px(props.minWidth),
536
+ minHeight: px(props.minHeight),
537
+ maxWidth: px(props.maxWidth),
538
+ maxHeight: px(props.maxHeight),
539
+ ...props.style,
540
+ }),
541
+ },
542
+ children,
543
+ key: props.key,
544
+ };
545
+ }
546
+
547
+ export function RotatedBox(propsOrChildren = {}, maybeChildren = undefined) {
548
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
549
+ const turns = props.quarterTurns ?? 0;
550
+
551
+ return {
552
+ tag: "div",
553
+ props: {
554
+ ...layoutProps(props, ["quarterTurns"]),
555
+ style: cleanStyle({
556
+ display: "inline-block",
557
+ transform: `rotate(${turns * 90}deg)`,
558
+ transformOrigin: "center",
559
+ ...props.style,
560
+ }),
561
+ },
562
+ children,
563
+ key: props.key,
564
+ };
565
+ }
566
+
567
+ export function SizedOverflowBox(
568
+ propsOrChildren = {},
569
+ maybeChildren = undefined,
570
+ ) {
571
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
572
+
573
+ return {
574
+ tag: "div",
575
+ props: {
576
+ ...layoutProps(props, ["size"]),
577
+ style: cleanStyle({
578
+ width: px(props.width ?? props.size?.width),
579
+ height: px(props.height ?? props.size?.height),
580
+ overflow: "visible",
581
+ position: "relative",
582
+ ...props.style,
583
+ }),
584
+ },
585
+ children,
586
+ key: props.key,
587
+ };
588
+ }
589
+
590
+ export function Transform(propsOrChildren = {}, maybeChildren = undefined) {
591
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
592
+ const angle = (value) => (typeof value === "number" ? `${value}deg` : value);
593
+ const translate = props.translate
594
+ ? `translate(${px(props.translate.x ?? 0)}, ${px(props.translate.y ?? 0)})`
595
+ : "";
596
+ const rotate = props.rotate === undefined ? "" : `rotate(${angle(props.rotate)})`;
597
+ const scale = props.scale === undefined ? "" : `scale(${props.scale})`;
598
+ const skew = props.skew
599
+ ? `skew(${angle(props.skew.x ?? 0)}, ${angle(props.skew.y ?? 0)})`
600
+ : "";
601
+
602
+ return {
603
+ tag: "div",
604
+ props: {
605
+ ...layoutProps(props, [
606
+ "transform",
607
+ "translate",
608
+ "rotate",
609
+ "scale",
610
+ "skew",
611
+ "origin",
612
+ ]),
613
+ style: cleanStyle({
614
+ display: "inline-block",
615
+ transform:
616
+ props.transform || [translate, rotate, scale, skew].filter(Boolean).join(" "),
617
+ transformOrigin: props.origin || "center",
618
+ ...props.style,
619
+ }),
620
+ },
621
+ children,
622
+ key: props.key,
623
+ };
624
+ }