@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,443 @@
1
+ import {
2
+ cleanStyle,
3
+ luminaTheme,
4
+ normalizeWidgetArgs,
5
+ omitProps,
6
+ px,
7
+ } from "./utils.js";
8
+
9
+ const icons = {
10
+ add: "+",
11
+ remove: "-",
12
+ close: "x",
13
+ check: "v",
14
+ search: "?",
15
+ menu: "=",
16
+ home: "^",
17
+ settings: "*",
18
+ person: "@",
19
+ info: "i",
20
+ warning: "!",
21
+ error: "!",
22
+ delete: "del",
23
+ edit: "edit",
24
+ save: "save",
25
+ star: "*",
26
+ favorite: "<3",
27
+ arrowBack: "<",
28
+ arrowForward: ">",
29
+ play: ">",
30
+ pause: "||",
31
+ };
32
+
33
+ export function Icon(propsOrName = {}, maybeProps = {}) {
34
+ const props =
35
+ typeof propsOrName === "string"
36
+ ? { ...maybeProps, name: propsOrName }
37
+ : propsOrName;
38
+ const {
39
+ name = "info",
40
+ label,
41
+ size = 24,
42
+ color = "currentColor",
43
+ style = {},
44
+ } = props;
45
+
46
+ return {
47
+ tag: "span",
48
+ props: {
49
+ ...omitProps(props, ["name", "label", "size", "color"]),
50
+ role: label ? "img" : undefined,
51
+ "aria-label": label,
52
+ "aria-hidden": label ? undefined : "true",
53
+ style: cleanStyle({
54
+ display: "inline-flex",
55
+ alignItems: "center",
56
+ justifyContent: "center",
57
+ width: px(size),
58
+ height: px(size),
59
+ color,
60
+ fontSize: px(size),
61
+ fontWeight: 700,
62
+ lineHeight: 1,
63
+ fontFamily: "system-ui, sans-serif",
64
+ flexShrink: 0,
65
+ ...style,
66
+ }),
67
+ },
68
+ children: [icons[name] || name],
69
+ key: props.key,
70
+ };
71
+ }
72
+
73
+ export function Image(props = {}) {
74
+ const {
75
+ src,
76
+ alt = "",
77
+ width,
78
+ height,
79
+ fit = "cover",
80
+ radius,
81
+ loading = "lazy",
82
+ style = {},
83
+ } = props;
84
+
85
+ return {
86
+ tag: "img",
87
+ props: {
88
+ ...omitProps(props, ["src", "alt", "width", "height", "fit", "radius", "loading"]),
89
+ src,
90
+ alt,
91
+ loading,
92
+ style: cleanStyle({
93
+ display: "block",
94
+ width: px(width, "100%"),
95
+ height: px(height, "auto"),
96
+ objectFit: fit,
97
+ borderRadius: px(radius),
98
+ backgroundColor: luminaTheme.colors.surfaceMuted,
99
+ ...style,
100
+ }),
101
+ },
102
+ children: [],
103
+ key: props.key,
104
+ };
105
+ }
106
+
107
+ export function CircleAvatar(propsOrChildren = {}, maybeChildren = undefined) {
108
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
109
+ const {
110
+ src,
111
+ alt = "",
112
+ initials,
113
+ size = 40,
114
+ backgroundColor = luminaTheme.colors.track,
115
+ color = luminaTheme.colors.text,
116
+ style = {},
117
+ } = props;
118
+
119
+ const content = src
120
+ ? [
121
+ Image({
122
+ src,
123
+ alt,
124
+ width: "100%",
125
+ height: "100%",
126
+ fit: "cover",
127
+ }),
128
+ ]
129
+ : children.length
130
+ ? children
131
+ : [String(initials || "?").slice(0, 2).toUpperCase()];
132
+
133
+ return {
134
+ tag: "div",
135
+ props: {
136
+ ...omitProps(props, [
137
+ "src",
138
+ "alt",
139
+ "initials",
140
+ "size",
141
+ "backgroundColor",
142
+ "color",
143
+ ]),
144
+ style: cleanStyle({
145
+ display: "inline-flex",
146
+ alignItems: "center",
147
+ justifyContent: "center",
148
+ width: px(size),
149
+ height: px(size),
150
+ borderRadius: "50%",
151
+ overflow: "hidden",
152
+ backgroundColor,
153
+ color,
154
+ fontSize: px(Math.max(12, Number(size) * 0.38 || 16)),
155
+ fontWeight: 700,
156
+ flexShrink: 0,
157
+ boxShadow: "inset 0 0 0 1px rgba(15, 23, 42, 0.06)",
158
+ ...style,
159
+ }),
160
+ },
161
+ children: content,
162
+ key: props.key,
163
+ };
164
+ }
165
+
166
+ export function Placeholder(props = {}) {
167
+ const {
168
+ width = "100%",
169
+ height = 120,
170
+ color = luminaTheme.colors.muted,
171
+ label = "Placeholder",
172
+ style = {},
173
+ } = props;
174
+
175
+ return {
176
+ tag: "div",
177
+ props: {
178
+ ...omitProps(props, ["width", "height", "color", "label"]),
179
+ role: "img",
180
+ "aria-label": label,
181
+ style: cleanStyle({
182
+ display: "flex",
183
+ alignItems: "center",
184
+ justifyContent: "center",
185
+ width: px(width),
186
+ height: px(height),
187
+ color,
188
+ border: `1px dashed ${luminaTheme.colors.borderStrong}`,
189
+ borderRadius: luminaTheme.radius.lg,
190
+ backgroundColor: luminaTheme.colors.surfaceMuted,
191
+ backgroundImage: `linear-gradient(135deg, transparent 48%, rgba(100, 116, 139, 0.30) 49%, rgba(100, 116, 139, 0.30) 51%, transparent 52%),
192
+ linear-gradient(45deg, transparent 48%, rgba(100, 116, 139, 0.30) 49%, rgba(100, 116, 139, 0.30) 51%, transparent 52%)`,
193
+ backgroundSize: "100% 100%",
194
+ fontSize: "12px",
195
+ fontWeight: 700,
196
+ ...style,
197
+ }),
198
+ },
199
+ children: [label],
200
+ key: props.key,
201
+ };
202
+ }
203
+
204
+ export function Badge(propsOrChildren = {}, maybeChildren = undefined) {
205
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
206
+ const {
207
+ label,
208
+ color = "#ef4444",
209
+ textColor = "#ffffff",
210
+ alignment = "topRight",
211
+ style = {},
212
+ } = props;
213
+
214
+ const position = {
215
+ topRight: { top: 0, right: 0, transform: "translate(35%, -35%)" },
216
+ topLeft: { top: 0, left: 0, transform: "translate(-35%, -35%)" },
217
+ bottomRight: { right: 0, bottom: 0, transform: "translate(35%, 35%)" },
218
+ bottomLeft: { left: 0, bottom: 0, transform: "translate(-35%, 35%)" },
219
+ }[alignment] || {};
220
+
221
+ return {
222
+ tag: "span",
223
+ props: {
224
+ ...omitProps(props, ["label", "color", "textColor", "alignment"]),
225
+ style: cleanStyle({
226
+ display: "inline-flex",
227
+ position: "relative",
228
+ ...style,
229
+ }),
230
+ },
231
+ children: [
232
+ ...children,
233
+ {
234
+ tag: "span",
235
+ props: {
236
+ style: cleanStyle({
237
+ position: "absolute",
238
+ minWidth: label === undefined ? "10px" : "18px",
239
+ height: label === undefined ? "10px" : "18px",
240
+ padding: label === undefined ? 0 : "0 5px",
241
+ borderRadius: "999px",
242
+ backgroundColor: color,
243
+ color: textColor,
244
+ display: "inline-flex",
245
+ alignItems: "center",
246
+ justifyContent: "center",
247
+ fontSize: "11px",
248
+ fontWeight: 700,
249
+ lineHeight: 1,
250
+ border: `2px solid ${luminaTheme.colors.surface}`,
251
+ boxShadow: "0 4px 10px rgba(15, 23, 42, 0.18)",
252
+ ...position,
253
+ }),
254
+ },
255
+ children: label === undefined ? [] : [label],
256
+ },
257
+ ],
258
+ key: props.key,
259
+ };
260
+ }
261
+
262
+ export function ClipRRect(propsOrChildren = {}, maybeChildren = undefined) {
263
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
264
+ return {
265
+ tag: "div",
266
+ props: {
267
+ ...omitProps(props, ["radius"]),
268
+ style: cleanStyle({
269
+ overflow: "hidden",
270
+ borderRadius: px(props.radius ?? 8),
271
+ ...props.style,
272
+ }),
273
+ },
274
+ children,
275
+ key: props.key,
276
+ };
277
+ }
278
+
279
+ export function ClipOval(propsOrChildren = {}, maybeChildren = undefined) {
280
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
281
+
282
+ return {
283
+ tag: "div",
284
+ props: {
285
+ ...omitProps(props),
286
+ style: cleanStyle({
287
+ overflow: "hidden",
288
+ borderRadius: "50%",
289
+ ...props.style,
290
+ }),
291
+ },
292
+ children,
293
+ key: props.key,
294
+ };
295
+ }
296
+
297
+ export function ClipRect(propsOrChildren = {}, maybeChildren = undefined) {
298
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
299
+
300
+ return {
301
+ tag: "div",
302
+ props: {
303
+ ...omitProps(props),
304
+ style: cleanStyle({
305
+ overflow: "hidden",
306
+ ...props.style,
307
+ }),
308
+ },
309
+ children,
310
+ key: props.key,
311
+ };
312
+ }
313
+
314
+ export function ClipPath(propsOrChildren = {}, maybeChildren = undefined) {
315
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
316
+
317
+ return {
318
+ tag: "div",
319
+ props: {
320
+ ...omitProps(props, ["path", "clipPath"]),
321
+ style: cleanStyle({
322
+ overflow: "hidden",
323
+ clipPath: props.clipPath || props.path,
324
+ ...props.style,
325
+ }),
326
+ },
327
+ children,
328
+ key: props.key,
329
+ };
330
+ }
331
+
332
+ export function FittedBox(propsOrChildren = {}, maybeChildren = undefined) {
333
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
334
+ const fitMap = {
335
+ contain: "contain",
336
+ cover: "cover",
337
+ fill: "fill",
338
+ fitWidth: "contain",
339
+ fitHeight: "contain",
340
+ none: "none",
341
+ scaleDown: "scale-down",
342
+ };
343
+
344
+ return {
345
+ tag: "div",
346
+ props: {
347
+ ...omitProps(props, ["fit", "alignment"]),
348
+ style: cleanStyle({
349
+ display: "flex",
350
+ alignItems: "center",
351
+ justifyContent: "center",
352
+ overflow: "hidden",
353
+ width: px(props.width, "100%"),
354
+ height: px(props.height, "100%"),
355
+ ...props.style,
356
+ }),
357
+ },
358
+ children: children.map((child) =>
359
+ child && typeof child === "object" && child.tag
360
+ ? {
361
+ ...child,
362
+ props: {
363
+ ...(child.props || {}),
364
+ style: {
365
+ objectFit: fitMap[props.fit] || props.fit || "contain",
366
+ maxWidth: "100%",
367
+ maxHeight: "100%",
368
+ ...(child.props?.style || {}),
369
+ },
370
+ },
371
+ }
372
+ : child,
373
+ ),
374
+ key: props.key,
375
+ };
376
+ }
377
+
378
+ export function Opacity(propsOrChildren = {}, maybeChildren = undefined) {
379
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
380
+
381
+ return {
382
+ tag: "div",
383
+ props: {
384
+ ...omitProps(props, ["opacity"]),
385
+ style: cleanStyle({
386
+ opacity: props.opacity ?? 1,
387
+ ...props.style,
388
+ }),
389
+ },
390
+ children,
391
+ key: props.key,
392
+ };
393
+ }
394
+
395
+ export function PhysicalModel(propsOrChildren = {}, maybeChildren = undefined) {
396
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
397
+ const elevation = props.elevation ?? 1;
398
+
399
+ return {
400
+ tag: "div",
401
+ props: {
402
+ ...omitProps(props, [
403
+ "elevation",
404
+ "color",
405
+ "shadowColor",
406
+ "borderRadius",
407
+ ]),
408
+ style: cleanStyle({
409
+ backgroundColor: props.color || luminaTheme.colors.surface,
410
+ borderRadius: px(props.borderRadius ?? 8),
411
+ boxShadow: `0 ${elevation}px ${elevation * 4}px ${
412
+ props.shadowColor || "rgba(15, 23, 42, 0.18)"
413
+ }`,
414
+ overflow: "hidden",
415
+ ...props.style,
416
+ }),
417
+ },
418
+ children,
419
+ key: props.key,
420
+ };
421
+ }
422
+
423
+ export function ShaderMask(propsOrChildren = {}, maybeChildren = undefined) {
424
+ const [props, children] = normalizeWidgetArgs(propsOrChildren, maybeChildren);
425
+
426
+ return {
427
+ tag: "div",
428
+ props: {
429
+ ...omitProps(props, ["shader", "blendMode"]),
430
+ style: cleanStyle({
431
+ display: "inline-block",
432
+ backgroundImage: props.shader,
433
+ WebkitBackgroundClip: props.blendMode === "text" ? "text" : undefined,
434
+ backgroundClip: props.blendMode === "text" ? "text" : undefined,
435
+ WebkitTextFillColor:
436
+ props.blendMode === "text" ? "transparent" : undefined,
437
+ ...props.style,
438
+ }),
439
+ },
440
+ children,
441
+ key: props.key,
442
+ };
443
+ }