@godxjp/ui 31.23.0 → 31.24.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` 31.23.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 31.24.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.
@@ -105,6 +105,11 @@
105
105
  "name": "mobileNavLabel",
106
106
  "type": "string"
107
107
  },
108
+ {
109
+ "description": "Visible text beside the drawer trigger's glyph (e.g. 'Pages'); it becomes the trigger's accessible name instead of the localized 'Open navigation'. The drawer closes on a link, a Sidebar row or a menu item, or an explicit close (Esc, overlay, close button, SheetClose) — other buttons such as a tree's expand toggle leave it open (gh#1133).",
110
+ "name": "mobileNavTriggerLabel",
111
+ "type": "string"
112
+ },
108
113
  {
109
114
  "description": "Controlled open state of the mobile drawer. Omit for AppShell-owned state.",
110
115
  "name": "mobileNavOpen",
@@ -125,6 +125,11 @@
125
125
  "name": "headerLoading",
126
126
  "type": "boolean"
127
127
  },
128
+ {
129
+ "description": "Ref to the page's <h1>; passing it also makes the heading programmatically focusable (tabIndex -1, outside the tab order, no focus ring) so a router can focus the new page title after client-side navigation (gh#1135).",
130
+ "name": "titleRef",
131
+ "type": "React.Ref<HTMLHeadingElement>"
132
+ },
128
133
  {
129
134
  "description": "Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`.",
130
135
  "name": "linkComponent",
@@ -25,6 +25,11 @@
25
25
  "name": "width",
26
26
  "type": "number | string"
27
27
  },
28
+ {
29
+ "description": "On SheetContent (Radix name, same as DialogContent): fires on every close before focus returns to the trigger. Call event.preventDefault() and focus your own target (e.g. the new page's heading after a drawer navigation) to send focus there instead (gh#1134).",
30
+ "name": "onCloseAutoFocus",
31
+ "type": "(event: Event) => void"
32
+ },
28
33
  {
29
34
  "defaultValue": "\"side\"",
30
35
  "description": "On SheetContent: the responsive drawer / detail-panel contract. \"side\" (default) always renders the physical `side` you named. \"auto\" renders the desktop side panel above --sheet-responsive-breakpoint-width (48rem/768px) and the mobile BOTTOM sheet at/below it, capped by --sheet-bottom-max-height (85dvh). \"bottom\" pins the bottom-sheet presentation.",
@@ -9,6 +9,11 @@
9
9
  "name": "ariaLabel",
10
10
  "type": "string"
11
11
  },
12
+ {
13
+ "description": "Ref to the navigation's scroll container (`.sb-nav-scroll`, the overflow-y element) — persist and restore its scrollTop across navigations or remounts (gh#1136).",
14
+ "name": "scrollRef",
15
+ "type": "React.Ref<HTMLElement>"
16
+ },
12
17
  {
13
18
  "description": "The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches.",
14
19
  "name": "activeId",
@@ -737,6 +737,11 @@
737
737
  "name": "headerLoading",
738
738
  "type": "boolean"
739
739
  },
740
+ {
741
+ "description": "Ref to the page's <h1>; passing it also makes the heading programmatically focusable (tabIndex -1, outside the tab order, no focus ring) so a router can focus the new page title after client-side navigation (gh#1135).",
742
+ "name": "titleRef",
743
+ "type": "React.Ref<HTMLHeadingElement>"
744
+ },
740
745
  {
741
746
  "description": "Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`.",
742
747
  "name": "linkComponent",
@@ -1191,6 +1196,11 @@
1191
1196
  "name": "mobileNavLabel",
1192
1197
  "type": "string"
1193
1198
  },
1199
+ {
1200
+ "description": "Visible text beside the drawer trigger's glyph (e.g. 'Pages'); it becomes the trigger's accessible name instead of the localized 'Open navigation'. The drawer closes on a link, a Sidebar row or a menu item, or an explicit close (Esc, overlay, close button, SheetClose) — other buttons such as a tree's expand toggle leave it open (gh#1133).",
1201
+ "name": "mobileNavTriggerLabel",
1202
+ "type": "string"
1203
+ },
1194
1204
  {
1195
1205
  "description": "Controlled open state of the mobile drawer. Omit for AppShell-owned state.",
1196
1206
  "name": "mobileNavOpen",
@@ -1501,6 +1511,11 @@
1501
1511
  "name": "ariaLabel",
1502
1512
  "type": "string"
1503
1513
  },
1514
+ {
1515
+ "description": "Ref to the navigation's scroll container (`.sb-nav-scroll`, the overflow-y element) — persist and restore its scrollTop across navigations or remounts (gh#1136).",
1516
+ "name": "scrollRef",
1517
+ "type": "React.Ref<HTMLElement>"
1518
+ },
1504
1519
  {
1505
1520
  "description": "The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches.",
1506
1521
  "name": "activeId",
@@ -7801,6 +7816,11 @@
7801
7816
  "name": "width",
7802
7817
  "type": "number | string"
7803
7818
  },
7819
+ {
7820
+ "description": "On SheetContent (Radix name, same as DialogContent): fires on every close before focus returns to the trigger. Call event.preventDefault() and focus your own target (e.g. the new page's heading after a drawer navigation) to send focus there instead (gh#1134).",
7821
+ "name": "onCloseAutoFocus",
7822
+ "type": "(event: Event) => void"
7823
+ },
7804
7824
  {
7805
7825
  "defaultValue": "\"side\"",
7806
7826
  "description": "On SheetContent: the responsive drawer / detail-panel contract. \"side\" (default) always renders the physical `side` you named. \"auto\" renders the desktop side panel above --sheet-responsive-breakpoint-width (48rem/768px) and the mobile BOTTOM sheet at/below it, capped by --sheet-bottom-max-height (85dvh). \"bottom\" pins the bottom-sheet presentation.",
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/v31.23.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v31.24.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": "31.23.0"
65
+ "version": "31.24.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: 181 components, 2129 design tokens,
4
- > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.23.0.
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 31.24.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@31.23.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@31.24.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/v31.23.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v31.24.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.
@@ -76,8 +76,14 @@ export interface SheetContentProps extends React.ComponentPropsWithRef<"section"
76
76
  * physical `side` the consumer named, at every viewport.
77
77
  */
78
78
  responsive?: SheetResponsiveProp;
79
+ /**
80
+ * Radix's name, same contract as `DialogContent`: fires on every close, before focus returns to
81
+ * the trigger. Call `event.preventDefault()` and focus your own target — the page heading after a
82
+ * navigation, say — to send focus there instead.
83
+ */
84
+ onCloseAutoFocus?: (event: Event) => void;
79
85
  }
80
- export declare function SheetContent({ side, className, children, showCloseButton, overlayClassName, forceMount: _forceMount, width, responsive, style, ref, ...props }: SheetContentProps): React.JSX.Element | null;
86
+ export declare function SheetContent({ side, className, children, showCloseButton, overlayClassName, forceMount: _forceMount, width, responsive, onCloseAutoFocus, style, ref, ...props }: SheetContentProps): React.JSX.Element | null;
81
87
  export declare namespace SheetContent {
82
88
  var displayName: string;
83
89
  }
@@ -118,6 +118,7 @@ function SheetContent({
118
118
  forceMount: _forceMount,
119
119
  width,
120
120
  responsive = "side",
121
+ onCloseAutoFocus,
121
122
  style,
122
123
  ref,
123
124
  ...props
@@ -136,7 +137,7 @@ function SheetContent({
136
137
  const dataState = state.isOpen ? "open" : "closed";
137
138
  const modal = React.useContext(SheetModalContext);
138
139
  const { contentRef, isMounted } = useNonModalPortal(state.isOpen, !modal);
139
- useOverlayCloseFocus(state.isOpen, void 0, modal ? void 0 : contentRef);
140
+ useOverlayCloseFocus(state.isOpen, onCloseAutoFocus, modal ? void 0 : contentRef);
140
141
  const overlayPortalContainer = useOverlayPortalContainer();
141
142
  const dialog = /* @__PURE__ */ jsx(
142
143
  RacDialog,
@@ -3,4 +3,4 @@ import type { AppShellProp } from "../../props/components/layout.prop.js";
3
3
  export type { AppShellProp, AppShellProp as AppShellProps, } from "../../props/components/layout.prop.js";
4
4
  /** Same rail boundary as shell-layout.css; SSR starts in docked mode. */
5
5
  export declare function useAppShellNavigationMode(): "drawer" | "docked";
6
- export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileNav, mobileNavLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
6
+ export declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, logoCompact, logoCompactBelow, breadcrumb, footer, children, sidebarCollapsed, responsiveNavigation, topbarSpan, navRail, navRailPosition, navRailEnd, navRailLabel, mobileNav, mobileNavLabel, mobileNavTriggerLabel, mobileNavOpen, onMobileNavOpenChange, }: AppShellProp): React.JSX.Element;
@@ -31,6 +31,7 @@ function AppShell({
31
31
  navRailLabel,
32
32
  mobileNav,
33
33
  mobileNavLabel,
34
+ mobileNavTriggerLabel,
34
35
  mobileNavOpen,
35
36
  onMobileNavOpenChange
36
37
  }) {
@@ -52,7 +53,7 @@ function AppShell({
52
53
  const setDrawerOpen = onMobileNavOpenChange ?? setUncontrolledOpen;
53
54
  const handleDrawerClick = (event) => {
54
55
  const target = event.target;
55
- const hit = target.closest("a[href], button, [role='menuitem']");
56
+ const hit = target.closest("a[href], .sb-nav-item, [role='menuitem']");
56
57
  if (!hit) {
57
58
  return;
58
59
  }
@@ -103,14 +104,17 @@ function AppShell({
103
104
  );
104
105
  const bar = !hasTopbarContent && !hasDrawer ? null : /* @__PURE__ */ jsxs("header", { className: "app-topbar ui-scale-fixed", "aria-label": t("layout.appShell.headerLabel"), children: [
105
106
  hasDrawer && /* @__PURE__ */ jsxs(Sheet, { open: drawerOpen, onOpenChange: setDrawerOpen, children: [
106
- /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
107
+ /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
107
108
  TopbarItem,
108
109
  {
109
110
  type: "button",
110
111
  className: "app-mobile-nav-trigger",
111
- "aria-label": t("layout.appShell.openNav"),
112
+ "aria-label": mobileNavTriggerLabel === void 0 ? t("layout.appShell.openNav") : void 0,
112
113
  "aria-haspopup": "dialog",
113
- children: /* @__PURE__ */ jsx(Menu, { className: "size-[var(--app-shell-mobile-nav-icon-size)]", "aria-hidden": "true" })
114
+ children: [
115
+ /* @__PURE__ */ jsx(Menu, { className: "size-[var(--app-shell-mobile-nav-icon-size)]", "aria-hidden": "true" }),
116
+ mobileNavTriggerLabel
117
+ ]
114
118
  }
115
119
  ) }),
116
120
  /* @__PURE__ */ jsxs(
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
2
2
  export type { PageContainerProp, PageContainerProp as PageContainerProps, PageContainerExtraProp, } from "../../props/components/layout.prop.js";
3
3
  export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop.js";
4
4
  export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react").JSX.Element;
5
- declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, density, variant, preset, headerLayout, headerScale, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
5
+ declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, toolbarPad, footerPad, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, titleRef, density, variant, preset, headerLayout, headerScale, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
6
6
  export declare const PageContainer: typeof PageContainerRoot & {
7
7
  Inset: typeof PageContainerInset;
8
8
  };
@@ -45,6 +45,7 @@ function PageContainerRoot({
45
45
  breadcrumbAriaLabel,
46
46
  linkComponent: LinkComponent = "a",
47
47
  headerLoading = false,
48
+ titleRef,
48
49
  density,
49
50
  variant = "default",
50
51
  preset = "default",
@@ -61,6 +62,7 @@ function PageContainerRoot({
61
62
  const { headerRef, revealed } = useFooterReveal(reveal);
62
63
  const { t } = useTranslation();
63
64
  const { start: extraStart, end: extraEnd } = resolvePageExtra(extra);
65
+ const titleFocus = titleRef === void 0 ? void 0 : { ref: titleRef, tabIndex: -1 };
64
66
  return /* @__PURE__ */ jsxs(
65
67
  "div",
66
68
  {
@@ -134,10 +136,17 @@ function PageContainerRoot({
134
136
  ),
135
137
  /* @__PURE__ */ jsxs("div", { className: "ui-page-header-row", children: [
136
138
  /* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading", children: [
137
- headerLoading ? /* @__PURE__ */ jsx("h1", { className: "ui-page-title ui-skeleton-block ui-page-title-placeholder", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("layout.pageHeader.loading") }) }) : status != null ? /* @__PURE__ */ jsxs("div", { className: "ui-page-header-title-row", children: [
138
- /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
139
+ headerLoading ? /* @__PURE__ */ jsx(
140
+ "h1",
141
+ {
142
+ ...titleFocus,
143
+ className: "ui-page-title ui-skeleton-block ui-page-title-placeholder",
144
+ children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("layout.pageHeader.loading") })
145
+ }
146
+ ) : status != null ? /* @__PURE__ */ jsxs("div", { className: "ui-page-header-title-row", children: [
147
+ /* @__PURE__ */ jsx("h1", { ...titleFocus, className: "ui-page-title", children: title }),
139
148
  /* @__PURE__ */ jsx("div", { className: "ui-page-header-status", children: status })
140
- ] }) : /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
149
+ ] }) : /* @__PURE__ */ jsx("h1", { ...titleFocus, className: "ui-page-title", children: title }),
141
150
  headerLoading ? (
142
151
  // Decorative only — the pending state is already announced once by the heading above,
143
152
  // so a second live placeholder here would double-announce it.
@@ -47,4 +47,4 @@ export { createSidebarLink } from "./sidebar-link.js";
47
47
  * Sidebar — data-driven vertical nav rail. Use {@link createSidebarLink} to adapt a router `Link`,
48
48
  * or `SidebarItem asChild` when composing rows by hand.
49
49
  */
50
- export declare function Sidebar({ ariaLabel: ariaLabelCamel, activeId, onSelect, sections, product, onProductClick, brand, collapsed, children, linkComponent, renderItem, footer, "aria-label": ariaLabel, }: SidebarProp): React.JSX.Element;
50
+ export declare function Sidebar({ ariaLabel: ariaLabelCamel, scrollRef, activeId, onSelect, sections, product, onProductClick, brand, collapsed, children, linkComponent, renderItem, footer, "aria-label": ariaLabel, }: SidebarProp): React.JSX.Element;
@@ -294,6 +294,7 @@ function CollapsedRow({ item, activeId, onSelect, linkComponent: LinkComponent }
294
294
  import { createSidebarLink } from "./sidebar-link.js";
295
295
  function Sidebar({
296
296
  ariaLabel: ariaLabelCamel,
297
+ scrollRef,
297
298
  activeId,
298
299
  onSelect,
299
300
  sections,
@@ -354,6 +355,7 @@ function Sidebar({
354
355
  /* @__PURE__ */ jsx(
355
356
  "nav",
356
357
  {
358
+ ref: scrollRef,
357
359
  className: "sb-nav-scroll",
358
360
  "aria-label": ariaLabel ?? ariaLabelCamel ?? t("layout.sidebar.ariaLabel"),
359
361
  children: children ?? resolvedSections.map((section, sectionIndex) => /* @__PURE__ */ jsx(
@@ -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": "31.23.0",
3
+ "version": "31.24.0",
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,
@@ -58,6 +58,12 @@ export type PageContainerProp = {
58
58
  * violation) so the page's heading outline never disappears mid-load.
59
59
  */
60
60
  headerLoading?: boolean;
61
+ /**
62
+ * Ref to the page's `<h1>`. Passing it also makes the heading programmatically focusable
63
+ * (`tabIndex={-1}`, outside the tab order, no focus ring), so a router can move focus to the new
64
+ * page's title after a client-side navigation — the pattern screen readers need to announce it.
65
+ */
66
+ titleRef?: React.Ref<HTMLHeadingElement>;
61
67
  /** @see PageContainerExtraProp */
62
68
  extra?: PageContainerExtraProp;
63
69
  /**
@@ -577,6 +583,14 @@ export type AppShellProp = {
577
583
  mobileNav?: ReactNode;
578
584
  /** Accessible title for the mobile navigation drawer. Defaults to the localized "Menu". */
579
585
  mobileNavLabel?: string;
586
+ /**
587
+ * Visible text beside the drawer trigger's glyph (e.g. "Pages"). It becomes the trigger's
588
+ * accessible name, replacing the localized "Open navigation" — omit it for the glyph-only
589
+ * trigger. The drawer closes when a link, a Sidebar row or a menu item inside it is activated,
590
+ * or on an explicit close (Esc, overlay, close button, `SheetClose`); other controls — a tree's
591
+ * expand toggle — leave it open.
592
+ */
593
+ mobileNavTriggerLabel?: string;
580
594
  /** Controlled open state of the mobile drawer. Omit for AppShell-owned (uncontrolled) state. */
581
595
  mobileNavOpen?: boolean;
582
596
  /** Change handler for the mobile drawer open state (pairs with `mobileNavOpen`). */
@@ -1371,6 +1385,11 @@ export type AppLauncherProp = {
1371
1385
  export type SidebarProp = {
1372
1386
  /** Accessible navigation landmark name; make it unique when multiple sidebars share a document. */
1373
1387
  ariaLabel?: string;
1388
+ /**
1389
+ * Ref to the navigation's scroll container (the element with `overflow-y: auto`), to persist
1390
+ * and restore its `scrollTop` across navigations or remounts.
1391
+ */
1392
+ scrollRef?: React.Ref<HTMLElement>;
1374
1393
  activeId: string;
1375
1394
  onSelect?: (id: string) => void;
1376
1395
  sections?: SidebarSectionProp[];
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "31.23.0",
2
+ "version": "31.24.0",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -896,6 +896,10 @@
896
896
  min-inline-size: 0;
897
897
  }
898
898
 
899
+ .ui-page-title[tabindex="-1"]:focus {
900
+ outline: none;
901
+ }
902
+
899
903
  .ui-page-header-title-row > .ui-page-title {
900
904
  min-inline-size: 0;
901
905
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "31.23.0",
4
- "godxUiMcp": "31.23.0",
3
+ "version": "31.24.0",
4
+ "godxUiMcp": "31.24.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",