@cosxai/ui 0.10.0 → 0.10.2
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/package.json +1 -1
- package/src/actionbar/ActionBar.tsx +87 -38
package/package.json
CHANGED
|
@@ -412,7 +412,9 @@ export function ActionBar({
|
|
|
412
412
|
boxShadow: "var(--ck-shadow-3)",
|
|
413
413
|
fontFamily: "var(--ck-font-sans)",
|
|
414
414
|
color: "var(--ck-text-primary)",
|
|
415
|
-
transition:
|
|
415
|
+
transition:
|
|
416
|
+
"background 260ms cubic-bezier(0.34, 1.56, 0.64, 1)," +
|
|
417
|
+
"border-color 260ms cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
416
418
|
...style,
|
|
417
419
|
}}
|
|
418
420
|
>
|
|
@@ -460,10 +462,10 @@ export function ActionBar({
|
|
|
460
462
|
) : null}
|
|
461
463
|
|
|
462
464
|
{/* Admin-mode toggle. Only renders when at least one registered
|
|
463
|
-
item has adminOnly=true.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
465
|
+
item has adminOnly=true. Borderless, transparent bg — same
|
|
466
|
+
visual weight as the drag grip and the item icons around
|
|
467
|
+
it. Muted stroke by default, accent stroke + subtle scale
|
|
468
|
+
when active. Icon: shield (elevated privileges). */}
|
|
467
469
|
{hasAdminItems && (
|
|
468
470
|
<button
|
|
469
471
|
type="button"
|
|
@@ -483,24 +485,28 @@ export function ActionBar({
|
|
|
483
485
|
marginLeft: 2,
|
|
484
486
|
marginRight: 2,
|
|
485
487
|
borderRadius: 999,
|
|
486
|
-
background:
|
|
488
|
+
background: "transparent",
|
|
487
489
|
color: adminMode
|
|
488
|
-
? "var(--ck-accent
|
|
490
|
+
? "var(--ck-accent, #4f46e5)"
|
|
489
491
|
: "var(--ck-text-secondary, #666)",
|
|
490
|
-
border:
|
|
491
|
-
? "1px solid var(--ck-accent, #4f46e5)"
|
|
492
|
-
: "1px solid var(--ck-border-subtle, #eee)",
|
|
492
|
+
border: "none",
|
|
493
493
|
cursor: "pointer",
|
|
494
|
-
|
|
494
|
+
transform: adminMode ? "scale(1.06)" : "scale(1)",
|
|
495
|
+
// Spring-ish easing so the tint + scale feel connected
|
|
496
|
+
// rather than mechanical — matches ck-actionbar-enter's
|
|
497
|
+
// curve for family consistency.
|
|
498
|
+
transition:
|
|
499
|
+
"color 260ms cubic-bezier(0.34, 1.56, 0.64, 1)," +
|
|
500
|
+
"transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
495
501
|
}}
|
|
496
502
|
>
|
|
497
503
|
<svg
|
|
498
|
-
width="
|
|
499
|
-
height="
|
|
504
|
+
width="16"
|
|
505
|
+
height="16"
|
|
500
506
|
viewBox="0 0 24 24"
|
|
501
507
|
fill="none"
|
|
502
508
|
stroke="currentColor"
|
|
503
|
-
strokeWidth="
|
|
509
|
+
strokeWidth="1.8"
|
|
504
510
|
strokeLinecap="round"
|
|
505
511
|
strokeLinejoin="round"
|
|
506
512
|
aria-hidden
|
|
@@ -578,8 +584,16 @@ export function ActionBar({
|
|
|
578
584
|
);
|
|
579
585
|
|
|
580
586
|
function renderEntry(entry: BuildEntry): ReactNode {
|
|
587
|
+
// Admin-only entries play a slide-in animation on mount so the
|
|
588
|
+
// reveal doesn't feel like items just popped in. Groups that
|
|
589
|
+
// contain any adminOnly items also get wrapped so the whole
|
|
590
|
+
// disclosure slides in together.
|
|
591
|
+
const isAdmin =
|
|
592
|
+
entry.item?.adminOnly === true ||
|
|
593
|
+
entry.groupItems?.some((it) => it.adminOnly === true) === true;
|
|
594
|
+
let node: ReactNode;
|
|
581
595
|
if (entry.kind === "flat" && entry.item) {
|
|
582
|
-
|
|
596
|
+
node = (
|
|
583
597
|
<ActionBarButton
|
|
584
598
|
key={entry.expansionKey}
|
|
585
599
|
icon={entry.item.icon}
|
|
@@ -592,27 +606,42 @@ export function ActionBar({
|
|
|
592
606
|
onClick={entry.item.onClick}
|
|
593
607
|
/>
|
|
594
608
|
);
|
|
609
|
+
} else {
|
|
610
|
+
const cat = entry.category!;
|
|
611
|
+
const catDef = categories[cat];
|
|
612
|
+
const hasActiveChild = entry.groupItems!.some((it) => it.active);
|
|
613
|
+
const isOpen = expandedKey === entry.expansionKey;
|
|
614
|
+
node = (
|
|
615
|
+
<ActionBarMenuGroup
|
|
616
|
+
key={entry.expansionKey}
|
|
617
|
+
label={catDef?.label ?? cat}
|
|
618
|
+
icon={catDef?.icon}
|
|
619
|
+
hasActiveChild={hasActiveChild}
|
|
620
|
+
isOpen={isOpen}
|
|
621
|
+
onToggle={() => setExpandedKey(isOpen ? null : entry.expansionKey)}
|
|
622
|
+
items={entry.groupItems!}
|
|
623
|
+
onItemClicked={(it) => {
|
|
624
|
+
it.onClick();
|
|
625
|
+
if (!it.keepGroupOpenOnClick) {
|
|
626
|
+
setExpandedKey(null);
|
|
627
|
+
}
|
|
628
|
+
}}
|
|
629
|
+
/>
|
|
630
|
+
);
|
|
595
631
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
632
|
+
if (!isAdmin) return node;
|
|
633
|
+
// Wrapper carries the spring-in animation via keyframes injected
|
|
634
|
+
// at module load (see bottom of file). Key includes adminMode so
|
|
635
|
+
// React remounts the wrapper each time it flips, replaying the
|
|
636
|
+
// animation cleanly.
|
|
600
637
|
return (
|
|
601
|
-
<
|
|
602
|
-
key={entry.expansionKey}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
items={entry.groupItems!}
|
|
609
|
-
onItemClicked={(it) => {
|
|
610
|
-
it.onClick();
|
|
611
|
-
if (!it.keepGroupOpenOnClick) {
|
|
612
|
-
setExpandedKey(null);
|
|
613
|
-
}
|
|
614
|
-
}}
|
|
615
|
-
/>
|
|
638
|
+
<span
|
|
639
|
+
key={`${entry.expansionKey}-admin`}
|
|
640
|
+
className="ck-actionbar-admin-item"
|
|
641
|
+
style={{ display: "inline-flex" }}
|
|
642
|
+
>
|
|
643
|
+
{node}
|
|
644
|
+
</span>
|
|
616
645
|
);
|
|
617
646
|
}
|
|
618
647
|
}
|
|
@@ -661,17 +690,37 @@ function StatusDotGlyph({ color, pulse }: StatusDotGlyphProps) {
|
|
|
661
690
|
);
|
|
662
691
|
}
|
|
663
692
|
|
|
664
|
-
// One global stylesheet entry for the pulse keyframe
|
|
665
|
-
// on first import. Idempotent —
|
|
693
|
+
// One global stylesheet entry for the pulse keyframe + admin-item
|
|
694
|
+
// entrance animation. Injected once on first import. Idempotent —
|
|
695
|
+
// re-imports check by id.
|
|
666
696
|
if (typeof document !== "undefined") {
|
|
667
697
|
const STYLE_ID = "ck-actionbar-status-pulse";
|
|
668
698
|
if (!document.getElementById(STYLE_ID)) {
|
|
669
699
|
const el = document.createElement("style");
|
|
670
700
|
el.id = STYLE_ID;
|
|
671
|
-
el.textContent =
|
|
701
|
+
el.textContent = `
|
|
702
|
+
@keyframes ck-actionbar-status-pulse {
|
|
672
703
|
0%, 100% { opacity: 1; }
|
|
673
704
|
50% { opacity: 0.45; }
|
|
674
|
-
}
|
|
705
|
+
}
|
|
706
|
+
@keyframes ck-actionbar-admin-enter {
|
|
707
|
+
0% {
|
|
708
|
+
opacity: 0;
|
|
709
|
+
transform: translateX(-6px) scale(0.92);
|
|
710
|
+
}
|
|
711
|
+
60% {
|
|
712
|
+
opacity: 1;
|
|
713
|
+
}
|
|
714
|
+
100% {
|
|
715
|
+
opacity: 1;
|
|
716
|
+
transform: translateX(0) scale(1);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
.ck-actionbar-admin-item {
|
|
720
|
+
animation: ck-actionbar-admin-enter 260ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
|
721
|
+
transform-origin: left center;
|
|
722
|
+
}
|
|
723
|
+
`;
|
|
675
724
|
document.head.appendChild(el);
|
|
676
725
|
}
|
|
677
726
|
}
|