@cosxai/ui 1.0.0-alpha.12 → 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,13 @@
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
+
5
+ ## 1.0.0-alpha.13 (2026-10-04)
6
+
7
+ - BottomSheet: the scrolling list reaches the sheet's sides (its padding
8
+ is the sheet's) and never pans sideways — a row bleeding to the edges
9
+ with `-mx-4` made it scroll 32px sideways and showed a scrollbar.
10
+
1
11
  ## 1.0.0-alpha.12 (2026-10-04)
2
12
 
3
13
  From the Metaroom app (Motion & Native Feel §04, Account):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "1.0.0-alpha.12",
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
 
@@ -236,7 +236,9 @@ export function BottomSheet({
236
236
  {description && <DialogPrimitive.Description className="m-0 px-1 pb-2 text-small text-fg-secondary">{description}</DialogPrimitive.Description>}
237
237
  </div>
238
238
  <div
239
- className={cn('flex min-h-0 flex-1 flex-col gap-0.5', two && !full ? 'overflow-hidden' : 'overflow-y-auto overscroll-contain')}
239
+ // Edge to edge (its padding is the sheet's): a row that bleeds
240
+ // to the sheet's sides with -mx-4 fits, and it never pans sideways
241
+ className={cn('-mx-4 flex min-h-0 flex-1 flex-col gap-0.5 px-4', two && !full ? 'overflow-hidden' : 'overflow-x-hidden overflow-y-auto overscroll-contain')}
240
242
  // At half height, a pull up on the list lifts the sheet first
241
243
  onWheel={(e) => two && !full && e.deltaY > 0 && setFull(true)}
242
244
  onTouchMove={() => two && !full && setFull(true)}