@astralui/core 0.1.11 → 0.1.13
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/dist/index.cjs +156 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +157 -38
- package/dist/index.js.map +1 -1
- package/dist/styles.css +7 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -367,11 +367,16 @@ function AstralPinInput({ length = 6, value, onChange, autoFocus }) {
|
|
|
367
367
|
i
|
|
368
368
|
)) });
|
|
369
369
|
}
|
|
370
|
-
function AstralSelect({ value, onChange, options, placeholder, disabled, searchable, searchPlaceholder, noResults, clearable, creatable }) {
|
|
370
|
+
function AstralSelect({ value, onChange, options, placeholder, disabled, searchable, searchPlaceholder, noResults, clearable, creatable, id, ariaLabel, ariaLabelledBy, clearLabel = "Clear selection" }) {
|
|
371
371
|
const [open, setOpen] = react.useState(false);
|
|
372
372
|
const [q, setQ] = react.useState("");
|
|
373
373
|
const triggerRef = react.useRef(null);
|
|
374
374
|
const menuRef = react.useRef(null);
|
|
375
|
+
const [activeIndex, setActiveIndex] = react.useState(-1);
|
|
376
|
+
const reactId = react.useId();
|
|
377
|
+
const baseId = id ?? `au-select-${reactId}`;
|
|
378
|
+
const listboxId = `${baseId}-listbox`;
|
|
379
|
+
const optionId = (i) => `${baseId}-opt-${i}`;
|
|
375
380
|
const [pos, setPos] = react.useState(null);
|
|
376
381
|
const reposition = react.useCallback(() => {
|
|
377
382
|
const el = triggerRef.current;
|
|
@@ -387,7 +392,10 @@ function AstralSelect({ value, onChange, options, placeholder, disabled, searcha
|
|
|
387
392
|
});
|
|
388
393
|
}, []);
|
|
389
394
|
react.useEffect(() => {
|
|
390
|
-
if (!open)
|
|
395
|
+
if (!open) {
|
|
396
|
+
setActiveIndex(-1);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
391
399
|
reposition();
|
|
392
400
|
const onScroll = () => reposition();
|
|
393
401
|
const onResize = () => setOpen(false);
|
|
@@ -410,28 +418,116 @@ function AstralSelect({ value, onChange, options, placeholder, disabled, searcha
|
|
|
410
418
|
const display = selected ? selected.label : value ?? "";
|
|
411
419
|
const filtered = searchable && q.trim() ? options.filter((o) => o.label.toLowerCase().includes(q.trim().toLowerCase())) : options;
|
|
412
420
|
const showCreate = !!creatable && !!q.trim() && !options.some((o) => o.label.toLowerCase() === q.trim().toLowerCase());
|
|
421
|
+
const rows = [...filtered.map((o) => o.value), ...showCreate ? [q.trim()] : []];
|
|
422
|
+
react.useEffect(() => {
|
|
423
|
+
if (!open || activeIndex < 0) return;
|
|
424
|
+
const el = menuRef.current?.querySelector(`#${CSS.escape(optionId(activeIndex))}`);
|
|
425
|
+
el?.scrollIntoView({ block: "nearest" });
|
|
426
|
+
}, [open, activeIndex, baseId]);
|
|
427
|
+
const commit = (index) => {
|
|
428
|
+
const picked = rows[index];
|
|
429
|
+
if (picked == null) return;
|
|
430
|
+
onChange(picked);
|
|
431
|
+
setOpen(false);
|
|
432
|
+
setQ("");
|
|
433
|
+
triggerRef.current?.focus();
|
|
434
|
+
};
|
|
435
|
+
const onKeyDown = (e) => {
|
|
436
|
+
if (disabled) return;
|
|
437
|
+
const key = e.key;
|
|
438
|
+
if (!open) {
|
|
439
|
+
if (key === "ArrowDown" || key === "ArrowUp" || key === "Enter" || key === " ") {
|
|
440
|
+
e.preventDefault();
|
|
441
|
+
setOpen(true);
|
|
442
|
+
setActiveIndex(0);
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (key === "Escape") {
|
|
447
|
+
e.preventDefault();
|
|
448
|
+
e.stopPropagation();
|
|
449
|
+
setOpen(false);
|
|
450
|
+
setQ("");
|
|
451
|
+
triggerRef.current?.focus();
|
|
452
|
+
} else if (key === "ArrowDown") {
|
|
453
|
+
e.preventDefault();
|
|
454
|
+
setActiveIndex((i) => rows.length ? (i + 1) % rows.length : -1);
|
|
455
|
+
} else if (key === "ArrowUp") {
|
|
456
|
+
e.preventDefault();
|
|
457
|
+
setActiveIndex((i) => rows.length ? i <= 0 ? rows.length - 1 : i - 1 : -1);
|
|
458
|
+
} else if (key === "Home") {
|
|
459
|
+
e.preventDefault();
|
|
460
|
+
setActiveIndex(rows.length ? 0 : -1);
|
|
461
|
+
} else if (key === "End") {
|
|
462
|
+
e.preventDefault();
|
|
463
|
+
setActiveIndex(rows.length - 1);
|
|
464
|
+
} else if (key === "Enter") {
|
|
465
|
+
e.preventDefault();
|
|
466
|
+
commit(activeIndex >= 0 ? activeIndex : 0);
|
|
467
|
+
} else if (key === "Tab") {
|
|
468
|
+
setOpen(false);
|
|
469
|
+
setQ("");
|
|
470
|
+
}
|
|
471
|
+
};
|
|
413
472
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "au-select", children: [
|
|
414
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
473
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
474
|
+
"button",
|
|
475
|
+
{
|
|
476
|
+
ref: triggerRef,
|
|
477
|
+
id: baseId,
|
|
478
|
+
type: "button",
|
|
479
|
+
className: "au-input au-select-trigger",
|
|
480
|
+
disabled,
|
|
481
|
+
role: "combobox",
|
|
482
|
+
"aria-expanded": open,
|
|
483
|
+
"aria-haspopup": "listbox",
|
|
484
|
+
"aria-controls": open ? listboxId : void 0,
|
|
485
|
+
"aria-activedescendant": open && !searchable && activeIndex >= 0 ? optionId(activeIndex) : void 0,
|
|
486
|
+
"aria-label": ariaLabel,
|
|
487
|
+
"aria-labelledby": ariaLabelledBy,
|
|
488
|
+
onClick: () => setOpen((o) => !o),
|
|
489
|
+
onKeyDown,
|
|
490
|
+
children: [
|
|
491
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: hasValue ? "" : "ph", children: hasValue ? display : placeholder }),
|
|
492
|
+
clearable && hasValue ? (
|
|
493
|
+
// Was a <span role="button" tabIndex={-1}>: announced as a button and
|
|
494
|
+
// reachable by nobody, so a keyboard user could not clear the field.
|
|
495
|
+
// It is a real button now, and it stops the click bubbling to the
|
|
496
|
+
// trigger so clearing does not also open the menu.
|
|
497
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
498
|
+
"span",
|
|
499
|
+
{
|
|
500
|
+
className: "au-select-caret au-select-clear",
|
|
501
|
+
role: "button",
|
|
502
|
+
tabIndex: 0,
|
|
503
|
+
"aria-label": clearLabel,
|
|
504
|
+
onClick: (e) => {
|
|
505
|
+
e.stopPropagation();
|
|
506
|
+
onChange("");
|
|
507
|
+
},
|
|
508
|
+
onKeyDown: (e) => {
|
|
509
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
510
|
+
e.preventDefault();
|
|
511
|
+
e.stopPropagation();
|
|
512
|
+
onChange("");
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconX, { size: 14 })
|
|
516
|
+
}
|
|
517
|
+
)
|
|
518
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(iconsReact.IconChevronDown, { size: 15, className: "au-select-caret" })
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
),
|
|
430
522
|
open && !disabled && pos && reactDom.createPortal(
|
|
431
523
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
432
524
|
"div",
|
|
433
525
|
{
|
|
434
526
|
ref: menuRef,
|
|
527
|
+
id: listboxId,
|
|
528
|
+
role: "listbox",
|
|
529
|
+
"aria-label": ariaLabel,
|
|
530
|
+
"aria-labelledby": ariaLabel ? void 0 : ariaLabelledBy,
|
|
435
531
|
className: "au-select-menu",
|
|
436
532
|
style: { left: pos.left, top: pos.top, bottom: pos.bottom, width: pos.width, maxHeight: pos.maxHeight },
|
|
437
533
|
children: [
|
|
@@ -441,35 +537,43 @@ function AstralSelect({ value, onChange, options, placeholder, disabled, searcha
|
|
|
441
537
|
autoFocus: true,
|
|
442
538
|
className: "au-select-search",
|
|
443
539
|
placeholder: searchPlaceholder,
|
|
540
|
+
"aria-label": searchPlaceholder,
|
|
541
|
+
"aria-controls": listboxId,
|
|
542
|
+
role: "combobox",
|
|
543
|
+
"aria-expanded": true,
|
|
544
|
+
"aria-autocomplete": "list",
|
|
545
|
+
"aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : void 0,
|
|
444
546
|
value: q,
|
|
445
|
-
onChange: (e) =>
|
|
547
|
+
onChange: (e) => {
|
|
548
|
+
setQ(e.currentTarget.value);
|
|
549
|
+
setActiveIndex(0);
|
|
550
|
+
},
|
|
551
|
+
onKeyDown
|
|
446
552
|
}
|
|
447
553
|
),
|
|
448
554
|
filtered.length === 0 && !showCreate && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "au-select-empty", children: noResults }),
|
|
449
|
-
filtered.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
450
|
-
"
|
|
555
|
+
filtered.map((o, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
556
|
+
"div",
|
|
451
557
|
{
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
},
|
|
558
|
+
id: optionId(i),
|
|
559
|
+
role: "option",
|
|
560
|
+
"aria-selected": o.value === value,
|
|
561
|
+
className: `au-select-opt${o.value === value ? " on" : ""}${i === activeIndex ? " active" : ""}`,
|
|
562
|
+
onMouseEnter: () => setActiveIndex(i),
|
|
563
|
+
onClick: () => commit(i),
|
|
459
564
|
children: o.label
|
|
460
565
|
},
|
|
461
566
|
o.value
|
|
462
567
|
)),
|
|
463
568
|
showCreate && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
464
|
-
"
|
|
569
|
+
"div",
|
|
465
570
|
{
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
},
|
|
571
|
+
id: optionId(filtered.length),
|
|
572
|
+
role: "option",
|
|
573
|
+
"aria-selected": false,
|
|
574
|
+
className: `au-select-opt au-select-create${filtered.length === activeIndex ? " active" : ""}`,
|
|
575
|
+
onMouseEnter: () => setActiveIndex(filtered.length),
|
|
576
|
+
onClick: () => commit(filtered.length),
|
|
473
577
|
children: [
|
|
474
578
|
"+ ",
|
|
475
579
|
q.trim()
|
|
@@ -1009,6 +1113,17 @@ var COLOR_MAP = {
|
|
|
1009
1113
|
replied: "#8b5cf6",
|
|
1010
1114
|
success: "#16a34a"
|
|
1011
1115
|
};
|
|
1116
|
+
var FG = {
|
|
1117
|
+
"#eab308": ["#854d0e", "#fde047"],
|
|
1118
|
+
"#06b6d4": ["#155e75", "#67e8f9"],
|
|
1119
|
+
"#EF8E2E": ["#9a4a06", "#fdba74"],
|
|
1120
|
+
"#9ca3af": ["#4b5563", "#d1d5db"],
|
|
1121
|
+
"#16a34a": ["#14532d", "#4ade80"],
|
|
1122
|
+
"#3b82f6": ["#1e40af", "#93c5fd"],
|
|
1123
|
+
"#ef4444": ["#991b1b", "#fca5a5"],
|
|
1124
|
+
"#8b5cf6": ["#5b21b6", "#c4b5fd"]
|
|
1125
|
+
};
|
|
1126
|
+
var SOLID = { "#16a34a": "#166534" };
|
|
1012
1127
|
var FILLED = /* @__PURE__ */ new Set(["running", "active", "online"]);
|
|
1013
1128
|
function StatusBadge({ status, label, color, filled }) {
|
|
1014
1129
|
const c = color ?? COLOR_MAP[status] ?? "#9ca3af";
|
|
@@ -1018,7 +1133,11 @@ function StatusBadge({ status, label, color, filled }) {
|
|
|
1018
1133
|
"span",
|
|
1019
1134
|
{
|
|
1020
1135
|
className: `au-sbadge${isFilled ? " filled" : ""}`,
|
|
1021
|
-
style: {
|
|
1136
|
+
style: {
|
|
1137
|
+
"--au-sb": c,
|
|
1138
|
+
...FG[c] ? { "--au-sb-fg": `light-dark(${FG[c][0]}, ${FG[c][1]})` } : {},
|
|
1139
|
+
...SOLID[c] ? { "--au-sb-solid": SOLID[c] } : {}
|
|
1140
|
+
},
|
|
1022
1141
|
title: typeof text === "string" ? text : void 0,
|
|
1023
1142
|
children: [
|
|
1024
1143
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "au-sbadge-dot" }),
|