@cosxai/ui 0.10.1 → 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 +82 -39
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,11 +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
|
-
|
|
467
|
-
Share / etc. */}
|
|
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). */}
|
|
468
469
|
{hasAdminItems && (
|
|
469
470
|
<button
|
|
470
471
|
type="button"
|
|
@@ -490,7 +491,13 @@ export function ActionBar({
|
|
|
490
491
|
: "var(--ck-text-secondary, #666)",
|
|
491
492
|
border: "none",
|
|
492
493
|
cursor: "pointer",
|
|
493
|
-
|
|
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)",
|
|
494
501
|
}}
|
|
495
502
|
>
|
|
496
503
|
<svg
|
|
@@ -504,14 +511,7 @@ export function ActionBar({
|
|
|
504
511
|
strokeLinejoin="round"
|
|
505
512
|
aria-hidden
|
|
506
513
|
>
|
|
507
|
-
|
|
508
|
-
which is exactly what admin mode does. Two horizontal
|
|
509
|
-
tracks with a knob on each; the active vs. inactive
|
|
510
|
-
distinction comes from stroke colour, not fill. */}
|
|
511
|
-
<line x1="4" y1="8" x2="20" y2="8" />
|
|
512
|
-
<line x1="4" y1="16" x2="20" y2="16" />
|
|
513
|
-
<circle cx="10" cy="8" r="2" fill="currentColor" />
|
|
514
|
-
<circle cx="15" cy="16" r="2" fill="currentColor" />
|
|
514
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
515
515
|
</svg>
|
|
516
516
|
</button>
|
|
517
517
|
)}
|
|
@@ -584,8 +584,16 @@ export function ActionBar({
|
|
|
584
584
|
);
|
|
585
585
|
|
|
586
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;
|
|
587
595
|
if (entry.kind === "flat" && entry.item) {
|
|
588
|
-
|
|
596
|
+
node = (
|
|
589
597
|
<ActionBarButton
|
|
590
598
|
key={entry.expansionKey}
|
|
591
599
|
icon={entry.item.icon}
|
|
@@ -598,27 +606,42 @@ export function ActionBar({
|
|
|
598
606
|
onClick={entry.item.onClick}
|
|
599
607
|
/>
|
|
600
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
|
+
);
|
|
601
631
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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.
|
|
606
637
|
return (
|
|
607
|
-
<
|
|
608
|
-
key={entry.expansionKey}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
items={entry.groupItems!}
|
|
615
|
-
onItemClicked={(it) => {
|
|
616
|
-
it.onClick();
|
|
617
|
-
if (!it.keepGroupOpenOnClick) {
|
|
618
|
-
setExpandedKey(null);
|
|
619
|
-
}
|
|
620
|
-
}}
|
|
621
|
-
/>
|
|
638
|
+
<span
|
|
639
|
+
key={`${entry.expansionKey}-admin`}
|
|
640
|
+
className="ck-actionbar-admin-item"
|
|
641
|
+
style={{ display: "inline-flex" }}
|
|
642
|
+
>
|
|
643
|
+
{node}
|
|
644
|
+
</span>
|
|
622
645
|
);
|
|
623
646
|
}
|
|
624
647
|
}
|
|
@@ -667,17 +690,37 @@ function StatusDotGlyph({ color, pulse }: StatusDotGlyphProps) {
|
|
|
667
690
|
);
|
|
668
691
|
}
|
|
669
692
|
|
|
670
|
-
// One global stylesheet entry for the pulse keyframe
|
|
671
|
-
// 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.
|
|
672
696
|
if (typeof document !== "undefined") {
|
|
673
697
|
const STYLE_ID = "ck-actionbar-status-pulse";
|
|
674
698
|
if (!document.getElementById(STYLE_ID)) {
|
|
675
699
|
const el = document.createElement("style");
|
|
676
700
|
el.id = STYLE_ID;
|
|
677
|
-
el.textContent =
|
|
701
|
+
el.textContent = `
|
|
702
|
+
@keyframes ck-actionbar-status-pulse {
|
|
678
703
|
0%, 100% { opacity: 1; }
|
|
679
704
|
50% { opacity: 0.45; }
|
|
680
|
-
}
|
|
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
|
+
`;
|
|
681
724
|
document.head.appendChild(el);
|
|
682
725
|
}
|
|
683
726
|
}
|