@cosxai/ui 1.0.0-alpha.13 → 1.0.0-alpha.14

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 CHANGED
@@ -1,3 +1,7 @@
1
+ ## 1.0.0-alpha.14
2
+
3
+ - 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).
4
+
1
5
  ## 1.0.0-alpha.13 (2026-10-04)
2
6
 
3
7
  - BottomSheet: the scrolling list reaches the sheet's sides (its padding
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.14",
4
4
  "description": "COSX Design System 3.0 \u2014 tokens, Tailwind v4 theme and React components",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -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
 
@@ -465,8 +474,8 @@ export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, la
465
474
  const renderAction = (a: ActionBarAction) => {
466
475
  const keys = a.shortcut ? formatShortcut(a.shortcut) : null;
467
476
  const Glyph = a.icon;
468
- const iconOnly = shown === 'icons' && Glyph;
469
- const button = (
477
+ const iconOnly = (shown === 'icons' || a.iconOnly) && Glyph;
478
+ const plain = (
470
479
  <button
471
480
  type="button"
472
481
  aria-label={iconOnly ? a.label : undefined}
@@ -479,16 +488,18 @@ export function ActionBar({ presentation, hidden: hiddenProp, maxActions = 6, la
479
488
  >
480
489
  {Glyph && <Glyph size={16} strokeWidth={1.75} aria-hidden />}
481
490
  {!iconOnly && <span>{a.label}</span>}
482
- {shown === 'full' && keys && <kbd className="font-sans text-meta font-medium opacity-55">{keys}</kbd>}
491
+ {shown === 'full' && keys && !iconOnly && <kbd className="font-sans text-meta font-medium opacity-55">{keys}</kbd>}
483
492
  </button>
484
493
  );
485
- const hint = a.disabledReason ?? (shown !== 'full' ? [a.label, keys].filter(Boolean).join(' · ') : null);
486
- return hint && (iconOnly || a.disabledReason || keys) ? (
487
- <Tooltip key={a.id} content={hint}>
488
- {button}
489
- </Tooltip>
490
- ) : (
491
- <Fragment key={a.id}>{button}</Fragment>
494
+ const button = a.wrap ? a.wrap(plain) : plain;
495
+ const rule = a.divider ? <span aria-hidden className="mx-1 h-5 w-px shrink-0 bg-white/20 ink:bg-ink/20" /> : null;
496
+ const hint = a.disabledReason ?? (shown !== 'full' || iconOnly ? [a.label, keys].filter(Boolean).join(' · ') : null);
497
+ const tipped = hint && (iconOnly || a.disabledReason || keys) ? <Tooltip content={hint}>{button}</Tooltip> : button;
498
+ return (
499
+ <Fragment key={a.id}>
500
+ {rule}
501
+ {tipped}
502
+ </Fragment>
492
503
  );
493
504
  };
494
505