@botiverse/raft-ui 0.67.0 → 0.68.0

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,12 @@
1
+ ## [0.68.0](https://github.com/botiverse/raft-design/compare/v0.67.0...v0.68.0) (2026-11-06)
2
+
3
+ ### Changed
4
+
5
+ - **MobileNavServerPopoverDestinations** (`@botiverse/raft-ui/wip`): more than 4 destinations take 3 columns, rows of 3 with the last row from the left (`data-layout="grid"`); each button keeps its height, icon, label size and hit area. 4 or fewer stay in one row (`data-layout="row"`). The count is of the element children, looking inside fragments. ([#1037](https://github.com/botiverse/raft-design/pull/1037))
6
+ - Why: a server tab that holds Artifacts, Members, Computers, Apps and Settings put five in a row, which truncated labels such as "Computadoras" at 360px; a third of the popover keeps them whole in every language.
7
+ - Affects: a server tab popover with more than 4 destinations; up to 4 are unchanged.
8
+ - Docs: https://raft-design.botiverse.build/components/mobile-nav/#the-server-tab
9
+
1
10
  ## [0.67.0](https://github.com/botiverse/raft-design/compare/v0.66.0...v0.67.0) (2026-11-06)
2
11
 
3
12
  ### Added
@@ -47,8 +47,17 @@ export type MobileNavServerPopoverRowLabelProps = ComponentPropsWithRef<"span">;
47
47
  /** A row's label: takes the room and truncates. */
48
48
  export declare function MobileNavServerPopoverRowLabel({ className, ...props }: MobileNavServerPopoverRowLabelProps): import("react").JSX.Element;
49
49
  export type MobileNavServerPopoverDestinationsProps = ComponentPropsWithRef<"div">;
50
- /** The server's own pages, side by side in equal cells; a `group`, named with `aria-label`. */
51
- export declare function MobileNavServerPopoverDestinations({ className, ...props }: MobileNavServerPopoverDestinationsProps): import("react").JSX.Element;
50
+ /**
51
+ * The server's own pages in equal cells; a `group`, named with `aria-label`.
52
+ * Up to 4 sit side by side in one row (`data-layout="row"`). More than 4
53
+ * take 3 columns, rows of 3 with the last row starting at the left
54
+ * (`data-layout="grid"`), so each label keeps a third of the popover and
55
+ * stays whole in every language. The count is of the element children, taken
56
+ * in React, looking inside fragments (a product puts the pages a guest lacks
57
+ * in one): counting them in CSS would take a structural pseudo-class on the
58
+ * children, which the WebKit selector rules in CLAUDE.md forbid.
59
+ */
60
+ export declare function MobileNavServerPopoverDestinations({ className, children, ...props }: MobileNavServerPopoverDestinationsProps): import("react").JSX.Element;
52
61
  export type MobileNavServerPopoverDestinationProps = ComponentPropsWithRef<"button">;
53
62
  /**
54
63
  * One of the server's pages: a 20px icon over a short label in a `span`, which
@@ -2,7 +2,7 @@ import { tv } from "../../lib/tv.mjs";
2
2
  import { MobileNavItem } from "./mobile-nav.mjs";
3
3
  import "../../lib/touch-drag.mjs";
4
4
  import { PopoverContent } from "../popover/popover.mjs";
5
- import { createContext, use, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
5
+ import { Children, Fragment, createContext, isValidElement, use, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  import { Popover } from "@base-ui/react/popover";
8
8
  //#region src/components/mobile-nav/mobile-nav-server.tsx
@@ -33,7 +33,7 @@ const mobileNavServer = tv({ slots: {
33
33
  rowLabel: ["min-w-0 flex-1 truncate"],
34
34
  rowBody: ["flex min-w-0 flex-1 flex-col"],
35
35
  rowDescription: ["min-w-0 truncate", "font-mono text-mono-meta font-normal text-foreground-hint"],
36
- destinations: ["grid shrink-0 grid-flow-col auto-cols-fr gap-1 p-1"],
36
+ destinations: ["grid shrink-0 grid-flow-col auto-cols-fr gap-1 p-1", "data-[layout=grid]:grid-flow-row data-[layout=grid]:grid-cols-3"],
37
37
  destination: [
38
38
  "relative flex min-w-0 cursor-default select-none flex-col items-center justify-center",
39
39
  "outline-none transition-colors",
@@ -231,14 +231,28 @@ function MobileNavServerPopoverRowLabel({ className, ...props }) {
231
231
  ...props
232
232
  });
233
233
  }
234
- /** The server's own pages, side by side in equal cells; a `group`, named with `aria-label`. */
235
- function MobileNavServerPopoverDestinations({ className, ...props }) {
234
+ /** Past this many pages, the row becomes a grid of 3 columns. */
235
+ const DESTINATIONS_IN_ONE_ROW = 4;
236
+ /**
237
+ * The server's own pages in equal cells; a `group`, named with `aria-label`.
238
+ * Up to 4 sit side by side in one row (`data-layout="row"`). More than 4
239
+ * take 3 columns, rows of 3 with the last row starting at the left
240
+ * (`data-layout="grid"`), so each label keeps a third of the popover and
241
+ * stays whole in every language. The count is of the element children, taken
242
+ * in React, looking inside fragments (a product puts the pages a guest lacks
243
+ * in one): counting them in CSS would take a structural pseudo-class on the
244
+ * children, which the WebKit selector rules in CLAUDE.md forbid.
245
+ */
246
+ function MobileNavServerPopoverDestinations({ className, children, ...props }) {
236
247
  const { destinations } = mobileNavServer();
248
+ const count = countElements(children);
237
249
  return /* @__PURE__ */ jsx("div", {
238
250
  role: "group",
239
251
  "data-slot": "mobile-nav-server-popover-destinations",
252
+ "data-layout": count > DESTINATIONS_IN_ONE_ROW ? "grid" : "row",
240
253
  className: destinations({ className }),
241
- ...props
254
+ ...props,
255
+ children
242
256
  });
243
257
  }
244
258
  /**
@@ -279,6 +293,15 @@ function MobileNavServerPopoverRowDescription({ className, ...props }) {
279
293
  ...props
280
294
  });
281
295
  }
296
+ /** The element children, counting a fragment's elements rather than the fragment. */
297
+ function countElements(children) {
298
+ let count = 0;
299
+ for (const child of Children.toArray(children)) {
300
+ if (!isValidElement(child)) continue;
301
+ count += child.type === Fragment ? countElements(child.props.children) : 1;
302
+ }
303
+ return count;
304
+ }
282
305
  function assignRef(ref, value) {
283
306
  if (typeof ref === "function") ref(value);
284
307
  else if (ref) ref.current = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botiverse/raft-ui",
3
- "version": "0.67.0",
3
+ "version": "0.68.0",
4
4
  "description": "React components and CSS foundations for the Raft Design System.",
5
5
  "keywords": [
6
6
  "agent-skills",