@godxjp/ui 30.1.1 → 30.2.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.
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 30.1.1.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 30.2.0.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
package/agent/index.json CHANGED
@@ -48,7 +48,7 @@
48
48
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v30.1.1/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v30.2.0/agent/index.json"
52
52
  },
53
53
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
@@ -62,5 +62,5 @@
62
62
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
63
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
64
  },
65
- "version": "30.1.1"
65
+ "version": "30.2.0"
66
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
3
  > A Japanese-enterprise React design system: 171 components, 2071 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 30.1.1.
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 30.2.0.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@30.1.1`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@30.2.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -26,7 +26,7 @@ that can only fetch URLs.
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v30.1.1/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v30.2.0/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
@@ -1,7 +1,8 @@
1
+ import * as React from "react";
1
2
  import type { TopbarProp } from "../../props/components/layout.prop.js";
2
3
  export type { TopbarProp, TopbarProp as TopbarProps } from "../../props/components/layout.prop.js";
3
4
  /**
4
5
  * Topbar — a PURE SLOT bar. It positions three clusters (`start` / `center` / `end`) and owns the
5
6
  * bar's flex layout; it does NOT bake any chrome.
6
7
  */
7
- export declare function Topbar({ start, center, end, className, children, height, overflow, pad, style, ...props }: TopbarProp): import("react").JSX.Element;
8
+ export declare function Topbar({ start, center, end, className, children, height, overflow, pad, style, ...props }: TopbarProp): React.JSX.Element;
@@ -1,6 +1,12 @@
1
+ "use client";
1
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { MoreHorizontal } from "lucide-react";
4
+ import * as React from "react";
5
+ import { useTranslation } from "../../i18n/use-translation.js";
2
6
  import { padStyle } from "../../lib/variants.js";
3
7
  import { cn } from "../../lib/utils.js";
8
+ import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
9
+ import { TopbarItem } from "./topbar-item.js";
4
10
  function Topbar({
5
11
  start,
6
12
  center,
@@ -13,23 +19,60 @@ function Topbar({
13
19
  style,
14
20
  ...props
15
21
  }) {
22
+ const barRef = React.useRef(null);
23
+ const foldable = overflow === "menu" && children == null;
24
+ const collapsed = useCollapsesToMenu(barRef, foldable);
25
+ const clusters = /* @__PURE__ */ jsxs(Fragment, { children: [
26
+ start != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-start", className: "ui-topbar-start", children: start }) : null,
27
+ center != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-center", className: "ui-topbar-center", children: center }) : null,
28
+ end != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-end", className: "ui-topbar-end", children: end }) : null
29
+ ] });
16
30
  return /* @__PURE__ */ jsx(
17
31
  "div",
18
32
  {
33
+ ref: barRef,
19
34
  "data-height": height,
20
35
  "data-overflow": overflow,
36
+ "data-collapsed": foldable ? String(collapsed) : void 0,
21
37
  style: { ...style, ...padStyle(pad, void 0) },
22
38
  "data-slot": "topbar",
23
39
  className: cn("ui-topbar", className),
24
40
  ...props,
25
- children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
26
- start != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-start", className: "ui-topbar-start", children: start }) : null,
27
- center != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-center", className: "ui-topbar-center", children: center }) : null,
28
- end != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-end", className: "ui-topbar-end", children: end }) : null
29
- ] })
41
+ children: children ?? (collapsed ? /* @__PURE__ */ jsx(TopbarOverflowMenu, { children: clusters }) : clusters)
30
42
  }
31
43
  );
32
44
  }
45
+ function TopbarOverflowMenu({ children }) {
46
+ const { t } = useTranslation();
47
+ return /* @__PURE__ */ jsxs(Popover, { children: [
48
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(TopbarItem, { "data-topbar-overflow-trigger": "", "aria-label": t("layout.topbar.more"), children: /* @__PURE__ */ jsx(MoreHorizontal, { "aria-hidden": "true" }) }) }),
49
+ /* @__PURE__ */ jsx(PopoverContent, { align: "end", width: "auto", children: /* @__PURE__ */ jsx("div", { "data-slot": "topbar-overflow", className: "ui-topbar-overflow", children }) })
50
+ ] });
51
+ }
52
+ function useCollapsesToMenu(ref, enabled) {
53
+ const [collapsed, setCollapsed] = React.useState(false);
54
+ const needed = React.useRef(0);
55
+ React.useLayoutEffect(() => {
56
+ const bar = ref.current;
57
+ if (!enabled || !bar) return void 0;
58
+ const update = () => {
59
+ if (!collapsed) {
60
+ if (bar.clientWidth > 0 && bar.scrollWidth - bar.clientWidth > 1) {
61
+ needed.current = bar.scrollWidth;
62
+ setCollapsed(true);
63
+ }
64
+ } else if (bar.clientWidth >= needed.current) {
65
+ setCollapsed(false);
66
+ }
67
+ };
68
+ update();
69
+ if (typeof ResizeObserver === "undefined") return void 0;
70
+ const observer = new ResizeObserver(update);
71
+ observer.observe(bar);
72
+ return () => observer.disconnect();
73
+ }, [ref, enabled, collapsed]);
74
+ return enabled && collapsed;
75
+ }
33
76
  export {
34
77
  Topbar
35
78
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "30.1.0",
3
+ "version": "30.1.1",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -243,6 +243,7 @@
243
243
  },
244
244
  "topbar": {
245
245
  "toggleSidebar": "Toggle sidebar",
246
+ "more": "More",
246
247
  "search": "Search",
247
248
  "searchPlaceholder": "Search...",
248
249
  "notifications": "Notifications",
@@ -239,6 +239,7 @@
239
239
  },
240
240
  "topbar": {
241
241
  "toggleSidebar": "サイドバーの切り替え",
242
+ "more": "その他",
242
243
  "search": "検索",
243
244
  "searchPlaceholder": "検索...",
244
245
  "notifications": "通知",
@@ -240,6 +240,7 @@
240
240
  },
241
241
  "topbar": {
242
242
  "toggleSidebar": "Bật/tắt thanh bên",
243
+ "more": "Thêm",
243
244
  "search": "Tìm kiếm",
244
245
  "searchPlaceholder": "Tìm kiếm...",
245
246
  "notifications": "Thông báo",
@@ -1424,12 +1424,17 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
1424
1424
  * - `"clip"` — the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted.
1425
1425
  * Still focusable, still announced, invisible and unreachable by pointer. Choose it only for a
1426
1426
  * bar you have measured as never overflowing.
1427
+ * - `"menu"` — when the clusters do not fit, the bar shows ONE "more" cell and the clusters
1428
+ * move into its popover, where they wrap (gh#914). Nothing is off-screen and nothing is
1429
+ * duplicated: each control is rendered once, in the bar or in the popover. Use it for a bar
1430
+ * that must survive a narrow viewport with enlarged text, where a scrolling bar can shrink to
1431
+ * a window narrower than one cell. Ignored with `children`.
1427
1432
  *
1428
1433
  * Independent of `TopbarItem hideBelow`, which REMOVES a cell at a step: dropping a cell is the
1429
1434
  * consumer saying it does not belong on a phone, and that remains the way to spend the budget
1430
1435
  * deliberately. This prop is only about what happens once there is no budget left.
1431
1436
  */
1432
- overflow?: "scroll" | "clip";
1437
+ overflow?: "scroll" | "clip" | "menu";
1433
1438
  /** Escape hatch — render fully custom bar content instead of the three slots. */
1434
1439
  children?: ReactNode;
1435
1440
  };
@@ -2084,6 +2084,36 @@
2084
2084
  min-inline-size: auto;
2085
2085
  }
2086
2086
 
2087
+ .ui-topbar[data-overflow="menu"] {
2088
+ overflow: hidden;
2089
+ min-inline-size: var(--topbar-item-min-width);
2090
+ }
2091
+
2092
+ .ui-topbar[data-overflow="menu"] > .ui-topbar-end {
2093
+ max-inline-size: none;
2094
+ min-inline-size: auto;
2095
+ }
2096
+
2097
+ .ui-topbar[data-overflow="menu"] > [data-topbar-overflow-trigger] {
2098
+ margin-inline-start: auto;
2099
+ }
2100
+
2101
+ .ui-topbar-overflow {
2102
+ display: flex;
2103
+ flex-wrap: wrap;
2104
+ align-items: center;
2105
+ gap: var(--topbar-gap);
2106
+
2107
+ max-inline-size: calc(100vi - 2 * var(--popover-space-inset) - var(--space-4));
2108
+ }
2109
+
2110
+ .ui-topbar-overflow > :is(.ui-topbar-start, .ui-topbar-center, .ui-topbar-end) {
2111
+ flex-wrap: wrap;
2112
+ margin-inline-start: 0;
2113
+ min-block-size: var(--topbar-item-min-width);
2114
+ overflow: visible;
2115
+ }
2116
+
2087
2117
  .ui-topbar-item {
2088
2118
  display: inline-flex;
2089
2119
  min-width: var(--topbar-item-min-width);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "30.1.1",
4
- "godxUiMcp": "30.1.1",
3
+ "version": "30.2.0",
4
+ "godxUiMcp": "30.2.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",