@cosxai/ui 1.0.0-alpha.13 → 1.0.0-alpha.15
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/components/ActionBar.tsx +27 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 1.0.0-alpha.15
|
|
2
|
+
|
|
3
|
+
- ActionBar `foldable={false}`: always shown in full — no fold button, and a stored fold is ignored (a page whose bar is its whole toolbar, like a document viewer).
|
|
4
|
+
|
|
5
|
+
## 1.0.0-alpha.14
|
|
6
|
+
|
|
7
|
+
- ActionBar: an action can carry `divider` (a rule before it), `iconOnly` (just the icon, label in the tooltip) and `wrap` + `wrapKey` (wrap the button in a menu or popover trigger so it opens something anchored to itself).
|
|
8
|
+
|
|
1
9
|
## 1.0.0-alpha.13 (2026-10-04)
|
|
2
10
|
|
|
3
11
|
- BottomSheet: the scrolling list reaches the sheet's sides (its padding
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
useRef,
|
|
12
12
|
useState,
|
|
13
13
|
type KeyboardEvent as ReactKeyboardEvent,
|
|
14
|
+
type ReactElement,
|
|
14
15
|
type ReactNode,
|
|
15
16
|
} from 'react';
|
|
16
17
|
|
|
@@ -35,6 +36,14 @@ export type ActionBarAction = {
|
|
|
35
36
|
disabledReason?: string | undefined;
|
|
36
37
|
/** Actions of a kind: more than two sharing a group fold into one menu. */
|
|
37
38
|
group?: string | undefined;
|
|
39
|
+
/** A thin rule before it, setting it apart from the actions on its left. */
|
|
40
|
+
divider?: boolean | undefined;
|
|
41
|
+
/** Always just the icon (a star, ⋯); the label names it for screen readers and the tooltip. */
|
|
42
|
+
iconOnly?: boolean | undefined;
|
|
43
|
+
/** Wrap the button, e.g. in a menu's or popover's trigger (asChild), so it opens something anchored to itself. */
|
|
44
|
+
wrap?: ((button: ReactElement) => ReactElement) | undefined;
|
|
45
|
+
/** Changes when what `wrap` shows changes (the bar re-renders only when an action's fields change). */
|
|
46
|
+
wrapKey?: string | undefined;
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
export type ActionBarSelection = {
|
|
@@ -126,7 +135,7 @@ function editable(t: EventTarget | null): boolean {
|
|
|
126
135
|
const dialogOpen = () => Boolean(document.querySelector('[role="dialog"][data-state="open"], [role="alertdialog"][data-state="open"]'));
|
|
127
136
|
|
|
128
137
|
const signature = (a: ActionBarAction[] | null | undefined) =>
|
|
129
|
-
a ? a.map((x) => [x.id, x.label, x.shortcut, x.active, x.disabled, x.disabledReason, x.group, x.icon?.displayName].join('\u0001')).join('\u0002') : '';
|
|
138
|
+
a ? a.map((x) => [x.id, x.label, x.shortcut, x.active, x.disabled, x.disabledReason, x.group, x.icon?.displayName, x.divider, x.iconOnly, x.wrapKey].join('\u0001')).join('\u0002') : '';
|
|
130
139
|
|
|
131
140
|
// ─── Provider ─────────────────────────────────────────────────────────
|
|
132
141
|
|
|
@@ -384,6 +393,8 @@ export type ActionBarProps = {
|
|
|
384
393
|
labels?: ActionBarLabels | undefined;
|
|
385
394
|
/** Lay it out in place (documentation, a demo) instead of floating: no fixed position, no dragging. */
|
|
386
395
|
inline?: boolean | undefined;
|
|
396
|
+
/** false: always shown in full — no fold button, a stored fold is ignored (a page whose bar is its whole toolbar). @default true */
|
|
397
|
+
foldable?: boolean | undefined;
|
|
387
398
|
className?: string | undefined;
|
|
388
399
|
};
|
|
389
400
|
|
|
@@ -400,7 +411,7 @@ const itemCls = cn(
|
|
|
400
411
|
* Narrowing drops shortcuts, then labels, then folds to a handle on the
|
|
401
412
|
* left edge. Drag by the grip; double-click it to reset.
|
|
402
413
|
*/
|
|
403
|
-
export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, labels = {}, inline = false, className }: ActionBarProps) {
|
|
414
|
+
export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, labels = {}, inline = false, foldable = true, className }: ActionBarProps) {
|
|
404
415
|
const ctx = useCtx();
|
|
405
416
|
const { idle, selection, mode, hidden, activity } = ctx.read();
|
|
406
417
|
const { prefs, setPrefs } = ctx;
|
|
@@ -411,7 +422,7 @@ export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, la
|
|
|
411
422
|
const state: ActionBarState = mode ? 'mode' : selection ? 'selection' : 'idle';
|
|
412
423
|
const auto = presentation ?? presentationFor(width);
|
|
413
424
|
// Entering a selection or a mode unfolds it; back in idle the user's fold state returns.
|
|
414
|
-
const folded = state === 'idle' && (auto === 'folded' || (presentation === undefined && prefs.folded));
|
|
425
|
+
const folded = foldable && state === 'idle' && (auto === 'folded' || (presentation === undefined && prefs.folded));
|
|
415
426
|
const shown: Exclude<ActionBarPresentation, 'folded'> = auto === 'folded' ? 'icons' : auto;
|
|
416
427
|
|
|
417
428
|
// Pull the bar back into view when the window shrinks.
|
|
@@ -465,8 +476,8 @@ export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, la
|
|
|
465
476
|
const renderAction = (a: ActionBarAction) => {
|
|
466
477
|
const keys = a.shortcut ? formatShortcut(a.shortcut) : null;
|
|
467
478
|
const Glyph = a.icon;
|
|
468
|
-
const iconOnly = shown === 'icons' && Glyph;
|
|
469
|
-
const
|
|
479
|
+
const iconOnly = (shown === 'icons' || a.iconOnly) && Glyph;
|
|
480
|
+
const plain = (
|
|
470
481
|
<button
|
|
471
482
|
type="button"
|
|
472
483
|
aria-label={iconOnly ? a.label : undefined}
|
|
@@ -479,16 +490,18 @@ export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, la
|
|
|
479
490
|
>
|
|
480
491
|
{Glyph && <Glyph size={16} strokeWidth={1.75} aria-hidden />}
|
|
481
492
|
{!iconOnly && <span>{a.label}</span>}
|
|
482
|
-
{shown === 'full' && keys && <kbd className="font-sans text-meta font-medium opacity-55">{keys}</kbd>}
|
|
493
|
+
{shown === 'full' && keys && !iconOnly && <kbd className="font-sans text-meta font-medium opacity-55">{keys}</kbd>}
|
|
483
494
|
</button>
|
|
484
495
|
);
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
496
|
+
const button = a.wrap ? a.wrap(plain) : plain;
|
|
497
|
+
const rule = a.divider ? <span aria-hidden className="mx-1 h-5 w-px shrink-0 bg-white/20 ink:bg-ink/20" /> : null;
|
|
498
|
+
const hint = a.disabledReason ?? (shown !== 'full' || iconOnly ? [a.label, keys].filter(Boolean).join(' · ') : null);
|
|
499
|
+
const tipped = hint && (iconOnly || a.disabledReason || keys) ? <Tooltip content={hint}>{button}</Tooltip> : button;
|
|
500
|
+
return (
|
|
501
|
+
<Fragment key={a.id}>
|
|
502
|
+
{rule}
|
|
503
|
+
{tipped}
|
|
504
|
+
</Fragment>
|
|
492
505
|
);
|
|
493
506
|
};
|
|
494
507
|
|
|
@@ -595,7 +608,7 @@ export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, la
|
|
|
595
608
|
</button>
|
|
596
609
|
</>
|
|
597
610
|
)}
|
|
598
|
-
{state === 'idle' && (
|
|
611
|
+
{state === 'idle' && foldable && (
|
|
599
612
|
<Tooltip content={`${labels.fold ?? 'Fold the action bar'} · \\`}>
|
|
600
613
|
<button type="button" aria-label={labels.fold ?? 'Fold the action bar'} onClick={() => setPrefs({ folded: true })} className={cn(itemCls, 'px-2 opacity-60 hover:opacity-100 max-md:hidden')}>
|
|
601
614
|
<ChevronsLeft size={16} aria-hidden />
|