@godxjp/ui 20.1.0 → 20.2.1

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.
Files changed (55) hide show
  1. package/dist/app/app-provider.js +37 -21
  2. package/dist/app/index.d.ts +2 -0
  3. package/dist/app/index.js +2 -0
  4. package/dist/app/storage.d.ts +17 -0
  5. package/dist/app/storage.js +28 -0
  6. package/dist/components/data-display/popover.d.ts +7 -1
  7. package/dist/components/data-display/popover.js +4 -0
  8. package/dist/components/data-display/service-launcher-card.js +4 -2
  9. package/dist/components/data-display/table.d.ts +60 -0
  10. package/dist/components/data-display/table.js +8 -0
  11. package/dist/components/data-entry/calendar.d.ts +1 -1
  12. package/dist/components/data-entry/calendar.js +11 -2
  13. package/dist/components/feedback/dialog.js +3 -0
  14. package/dist/components/feedback/sheet.js +3 -0
  15. package/dist/components/feedback/tooltip.js +3 -0
  16. package/dist/components/general/button.d.ts +1 -0
  17. package/dist/components/general/button.js +2 -0
  18. package/dist/components/layout/app-launcher.d.ts +1 -1
  19. package/dist/components/layout/app-launcher.js +43 -10
  20. package/dist/components/layout/app-shell.d.ts +1 -1
  21. package/dist/components/layout/app-shell.js +14 -7
  22. package/dist/components/layout/auth-shell.d.ts +1 -1
  23. package/dist/components/layout/auth-shell.js +2 -0
  24. package/dist/components/layout/org-switcher.js +22 -2
  25. package/dist/components/layout/sidebar.js +2 -1
  26. package/dist/components/navigation/dropdown-menu.js +5 -0
  27. package/dist/components/ui/hover-card.js +3 -0
  28. package/dist/i18n/index.d.ts +1 -1
  29. package/dist/i18n/index.js +3 -1
  30. package/dist/i18n/messages/en.json +8 -1
  31. package/dist/i18n/messages/ja.json +8 -1
  32. package/dist/i18n/messages/vi.json +8 -1
  33. package/dist/i18n/translate.d.ts +19 -0
  34. package/dist/i18n/translate.js +24 -0
  35. package/dist/lib/overlay-portal.d.ts +18 -0
  36. package/dist/lib/overlay-portal.js +17 -0
  37. package/dist/props/components/app.prop.d.ts +13 -2
  38. package/dist/props/components/general.prop.d.ts +13 -0
  39. package/dist/props/components/layout.prop.d.ts +95 -5
  40. package/dist/styles/card-layout.css +10 -1
  41. package/dist/styles/chart-layout.css +2 -1
  42. package/dist/styles/control.css +5 -0
  43. package/dist/styles/data-display-layout.css +7 -1
  44. package/dist/styles/navigation-layout.css +3 -1
  45. package/dist/styles/shell-layout.css +496 -22
  46. package/dist/tokens/components/control.css +2 -1
  47. package/dist/tokens/components/shell.css +59 -3
  48. package/dist/tokens/foundation.css +2 -0
  49. package/docs/data-display/list-row.tsx +6 -6
  50. package/docs/layout/app-launcher.tsx +44 -0
  51. package/docs/layout/app-shell-arrangements.tsx +5 -5
  52. package/docs/showcase/settings-security-mfa.tsx +3 -3
  53. package/package.json +7 -4
  54. package/scripts/_agent-setup.mjs +57 -7
  55. package/scripts/visual-audit.mjs +45 -7
@@ -4,9 +4,9 @@ import * as React from "react";
4
4
  import { Menu } from "lucide-react";
5
5
  import { useMediaQuery } from "../../lib/hooks.js";
6
6
  import { useTranslation } from "../../i18n/use-translation.js";
7
- import { Button } from "../general/button.js";
8
7
  import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../feedback/sheet.js";
9
8
  import { NavSurfaceProvider } from "./nav-surface.js";
9
+ import { TopbarItem } from "./topbar-item.js";
10
10
  function useAppShellNavigationMode() {
11
11
  return useMediaQuery("(width <= 56.25rem)") ? "drawer" : "docked";
12
12
  }
@@ -23,6 +23,8 @@ function AppShell({
23
23
  responsiveNavigation = "drawer",
24
24
  topbarSpan = "content",
25
25
  navRail,
26
+ navRailPosition = "start",
27
+ navRailEnd,
26
28
  navRailLabel,
27
29
  mobileNav,
28
30
  mobileNavLabel,
@@ -32,8 +34,12 @@ function AppShell({
32
34
  const { t } = useTranslation();
33
35
  const navigationMode = useAppShellNavigationMode();
34
36
  const hasSidebar = sidebar !== void 0 && sidebar !== null && sidebar !== false;
35
- const drawerNav = mobileNav !== void 0 ? mobileNav : navRail !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ const railContent = navRail === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
36
38
  navRail,
39
+ navRailEnd !== void 0 ? /* @__PURE__ */ jsx("div", { className: "app-nav-rail-end", children: navRailEnd }) : null
40
+ ] });
41
+ const drawerNav = mobileNav !== void 0 ? mobileNav : navRail !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
42
+ railContent,
37
43
  sidebar
38
44
  ] }) : sidebar;
39
45
  const hasDrawer = responsiveNavigation === "drawer" && drawerNav != null && drawerNav !== false;
@@ -82,18 +88,18 @@ function AppShell({
82
88
  "aside",
83
89
  {
84
90
  className: "app-nav-rail",
91
+ "data-orientation": navRailPosition === "top" || navRailPosition === "bottom" ? "horizontal" : "vertical",
92
+ "data-edge": navRailPosition,
85
93
  "aria-label": navRailLabel ?? t("layout.appShell.navRailLabel"),
86
- children: navRail
94
+ children: railContent
87
95
  }
88
96
  );
89
97
  const bar = !hasTopbarContent && !hasDrawer ? null : /* @__PURE__ */ jsxs("header", { className: "app-topbar ui-scale-fixed", "aria-label": t("layout.appShell.headerLabel"), children: [
90
98
  hasDrawer && /* @__PURE__ */ jsxs(Sheet, { open: drawerOpen, onOpenChange: setDrawerOpen, children: [
91
99
  /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
92
- Button,
100
+ TopbarItem,
93
101
  {
94
102
  type: "button",
95
- variant: "ghost",
96
- size: "sm",
97
103
  className: "app-mobile-nav-trigger",
98
104
  "aria-label": t("layout.appShell.openNav"),
99
105
  "aria-haspopup": "dialog",
@@ -115,7 +121,7 @@ function AppShell({
115
121
  className: "app-mobile-nav-body px-[var(--app-shell-mobile-nav-inset)]",
116
122
  onClick: handleDrawerClick,
117
123
  children: railInDrawer ? /* @__PURE__ */ jsxs("div", { className: "app-mobile-nav-columns", children: [
118
- /* @__PURE__ */ jsx("div", { className: "app-mobile-nav-rail", children: navRail }),
124
+ /* @__PURE__ */ jsx("div", { className: "app-mobile-nav-rail", children: railContent }),
119
125
  /* @__PURE__ */ jsx(NavSurfaceProvider, { surface: "drawer", children: /* @__PURE__ */ jsx("div", { className: "app-mobile-nav-sections", children: sidebar }) })
120
126
  ] }) : /* @__PURE__ */ jsx(NavSurfaceProvider, { surface: "drawer", children: drawerNav })
121
127
  }
@@ -137,6 +143,7 @@ function AppShell({
137
143
  "data-topbar": hasTopbarContent ? void 0 : "none",
138
144
  "data-topbar-span": topbarSpan === "full" ? "full" : void 0,
139
145
  "data-nav-rail": navRail !== void 0 ? "" : void 0,
146
+ "data-nav-rail-position": navRail !== void 0 ? navRailPosition : void 0,
140
147
  children: [
141
148
  topbarSpan === "full" ? /* @__PURE__ */ jsxs(Fragment, { children: [
142
149
  bar,
@@ -7,4 +7,4 @@ export type { AuthShellProp, AuthShellProp as AuthShellProps, } from "../../prop
7
7
  * locale picker, a theme toggle), and `measure="wide"` opens the content slot to the split login
8
8
  * (a brand panel beside the card) that used to have no shell of its own.
9
9
  */
10
- export declare function AuthShell({ brand, actions, footer, children, variant, preset, measure, density, className, }: AuthShellProp): import("react").JSX.Element;
10
+ export declare function AuthShell({ brand, actions, footer, children, variant, preset, measure, align, density, className, }: AuthShellProp): import("react").JSX.Element;
@@ -10,6 +10,7 @@ function AuthShell({
10
10
  variant = "default",
11
11
  preset = "default",
12
12
  measure = "default",
13
+ align,
13
14
  density,
14
15
  className
15
16
  }) {
@@ -22,6 +23,7 @@ function AuthShell({
22
23
  "data-variant": variant,
23
24
  "data-preset": preset === "default" ? void 0 : preset,
24
25
  "data-measure": measure === "default" ? void 0 : measure,
26
+ "data-align": align,
25
27
  "data-density": resolvedDensity,
26
28
  className: cn("ui-auth-shell", className),
27
29
  children: [
@@ -2,11 +2,13 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Check, ChevronsUpDown, Loader2, RotateCcw } from "lucide-react";
5
+ import { useTranslation } from "../../i18n/use-translation.js";
5
6
  import { cn } from "../../lib/utils.js";
6
7
  import {
7
8
  Dialog,
8
9
  DialogBody,
9
10
  DialogContent,
11
+ DialogFooter,
10
12
  DialogHeader,
11
13
  DialogTitle,
12
14
  DialogTrigger
@@ -23,6 +25,7 @@ import {
23
25
  import {
24
26
  Sheet,
25
27
  SheetBody,
28
+ SheetFooter,
26
29
  SheetContent,
27
30
  SheetHeader,
28
31
  SheetTrigger,
@@ -158,6 +161,7 @@ function OrgSwitcher({
158
161
  className,
159
162
  ...rest
160
163
  }) {
164
+ const { t } = useTranslation();
161
165
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
162
166
  const controlled = open !== void 0;
163
167
  const resolvedOpen = controlled ? open : uncontrolledOpen;
@@ -201,6 +205,20 @@ function OrgSwitcher({
201
205
  close: () => setOpen(false)
202
206
  }
203
207
  );
208
+ const keyboardLegend = /* @__PURE__ */ jsxs("div", { className: "ui-org-switcher-legend", children: [
209
+ /* @__PURE__ */ jsxs("span", { className: "ui-org-switcher-hint", children: [
210
+ /* @__PURE__ */ jsx("kbd", { className: "kbd", "aria-hidden": "true", children: "\u2191\u2193" }),
211
+ t("layout.orgSwitcher.hintMove")
212
+ ] }),
213
+ /* @__PURE__ */ jsxs("span", { className: "ui-org-switcher-hint", children: [
214
+ /* @__PURE__ */ jsx("kbd", { className: "kbd", "aria-hidden": "true", children: "\u21B5" }),
215
+ t("layout.orgSwitcher.hintSelect")
216
+ ] }),
217
+ /* @__PURE__ */ jsxs("span", { className: "ui-org-switcher-hint", children: [
218
+ /* @__PURE__ */ jsx("kbd", { className: "kbd", "aria-hidden": "true", children: "esc" }),
219
+ t("layout.orgSwitcher.hintClose")
220
+ ] })
221
+ ] });
204
222
  if (sheet) {
205
223
  return /* @__PURE__ */ jsx("div", { className: cn("ui-org-switcher", className), "data-collapsed": collapsed || void 0, children: /* @__PURE__ */ jsxs(Sheet, { open: resolvedOpen, onOpenChange: setOpen, children: [
206
224
  /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: trigger }),
@@ -214,7 +232,8 @@ function OrgSwitcher({
214
232
  },
215
233
  children: [
216
234
  /* @__PURE__ */ jsx(SheetHeader, { title: labels.title }),
217
- /* @__PURE__ */ jsx(SheetBody, { children: panel })
235
+ /* @__PURE__ */ jsx(SheetBody, { children: panel }),
236
+ /* @__PURE__ */ jsx(SheetFooter, { children: keyboardLegend })
218
237
  ]
219
238
  }
220
239
  )
@@ -225,7 +244,8 @@ function OrgSwitcher({
225
244
  /* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: trigger }),
226
245
  /* @__PURE__ */ jsxs(DialogContent, { className: "ui-org-switcher-dialog", children: [
227
246
  /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: labels.title }) }),
228
- /* @__PURE__ */ jsx(DialogBody, { children: panel })
247
+ /* @__PURE__ */ jsx(DialogBody, { children: panel }),
248
+ /* @__PURE__ */ jsx(DialogFooter, { children: keyboardLegend })
229
249
  ] })
230
250
  ] }) });
231
251
  }
@@ -304,8 +304,9 @@ function Sidebar({
304
304
  const resolvedSections = sections ?? [];
305
305
  const surface = useNavSurface();
306
306
  collapsed = surface === "drawer" ? false : collapsed;
307
+ const brandNode = typeof brand === "function" ? brand(collapsed) : brand;
307
308
  return /* @__PURE__ */ jsxs("div", { className: "sb-root", "data-collapsed": collapsed ? "true" : void 0, children: [
308
- brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children: brand }) : product ? (() => {
309
+ brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children: brandNode }) : product ? (() => {
309
310
  const interactive = onProductClick != null;
310
311
  const mark = /* @__PURE__ */ jsx(
311
312
  "span",
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useOverlayPortalContainer } from "../../lib/overlay-portal.js";
3
4
  import * as React from "react";
4
5
  import {
5
6
  Header,
@@ -145,9 +146,11 @@ function DropdownMenuContent({
145
146
  void forceMount;
146
147
  const { modal } = React.useContext(DropdownMenuModalContext);
147
148
  const anchor = placement ? DROPDOWN_MENU_PLACEMENT[placement] : void 0;
149
+ const overlayPortalContainer = useOverlayPortalContainer();
148
150
  return /* @__PURE__ */ jsx(DropdownMenuPortal, { children: /* @__PURE__ */ jsxs(
149
151
  Popover,
150
152
  {
153
+ UNSTABLE_portalContainer: overlayPortalContainer,
151
154
  "data-slot": "dropdown-menu-content",
152
155
  isNonModal: !modal,
153
156
  placement: toPlacement(side ?? anchor?.side, align ?? anchor?.align),
@@ -384,9 +387,11 @@ function DropdownMenuSubContent({
384
387
  void forceMount;
385
388
  void hideWhenDetached;
386
389
  void sticky;
390
+ const overlayPortalContainer = useOverlayPortalContainer();
387
391
  return /* @__PURE__ */ jsx(
388
392
  Popover,
389
393
  {
394
+ UNSTABLE_portalContainer: overlayPortalContainer,
390
395
  "data-slot": "dropdown-menu-sub-content",
391
396
  placement: align ? toPlacement("right", align) : void 0,
392
397
  offset: sideOffset,
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
+ import { useOverlayPortalContainer } from "../../lib/overlay-portal.js";
3
4
  import * as React from "react";
4
5
  import { chain, mergeRefs } from "@react-aria/utils";
5
6
  import { Popover as AriaPopover } from "react-aria-components";
@@ -142,9 +143,11 @@ function HoverCardContent({
142
143
  ...props
143
144
  }) {
144
145
  const card = useHoverCard("HoverCardContent");
146
+ const overlayPortalContainer = useOverlayPortalContainer();
145
147
  return /* @__PURE__ */ jsx(
146
148
  AriaPopover,
147
149
  {
150
+ UNSTABLE_portalContainer: overlayPortalContainer,
148
151
  isOpen: card.open,
149
152
  onOpenChange: card.setOpenImmediately,
150
153
  isNonModal: true,
@@ -1,3 +1,3 @@
1
- export { translate, translateCurrent, syncI18nLocale, resetI18nLocale, MESSAGE_CATALOG, } from "./translate.js";
1
+ export { translate, translateCurrent, syncI18nLocale, resetI18nLocale, MESSAGE_CATALOG, registerMessages, } from "./translate.js";
2
2
  export type { Messages, MessageKey, TranslateParams } from "./translate.js";
3
3
  export { useTranslation, usePickerLocales } from "./use-translation.js";
@@ -3,11 +3,13 @@ import {
3
3
  translateCurrent,
4
4
  syncI18nLocale,
5
5
  resetI18nLocale,
6
- MESSAGE_CATALOG
6
+ MESSAGE_CATALOG,
7
+ registerMessages
7
8
  } from "./translate.js";
8
9
  import { useTranslation, usePickerLocales } from "./use-translation.js";
9
10
  export {
10
11
  MESSAGE_CATALOG,
12
+ registerMessages,
11
13
  resetI18nLocale,
12
14
  syncI18nLocale,
13
15
  translate,
@@ -19,7 +19,9 @@
19
19
  "dataEntry": {
20
20
  "calendar": {
21
21
  "today": "Today",
22
- "close": "Close"
22
+ "close": "Close",
23
+ "nav": "{label} navigation",
24
+ "name": "Calendar"
23
25
  },
24
26
  "datePicker": {
25
27
  "placeholder": "Select date",
@@ -240,6 +242,11 @@
240
242
  "deniedDescription": "Ask an administrator to grant you access.",
241
243
  "loading": "Loading roles…",
242
244
  "noDetail": "Select a role to see its detail"
245
+ },
246
+ "orgSwitcher": {
247
+ "hintMove": "Move",
248
+ "hintSelect": "Select",
249
+ "hintClose": "Close"
243
250
  }
244
251
  },
245
252
  "dataDisplay": {
@@ -19,7 +19,9 @@
19
19
  "dataEntry": {
20
20
  "calendar": {
21
21
  "today": "今日",
22
- "close": "閉じる"
22
+ "close": "閉じる",
23
+ "nav": "{label}のナビゲーション",
24
+ "name": "カレンダー"
23
25
  },
24
26
  "datePicker": {
25
27
  "placeholder": "日付を選択",
@@ -234,6 +236,11 @@
234
236
  "deniedDescription": "閲覧するには管理者に権限の付与を依頼してください。",
235
237
  "loading": "ロールを読み込み中…",
236
238
  "noDetail": "ロールを選択すると詳細が表示されます"
239
+ },
240
+ "orgSwitcher": {
241
+ "hintMove": "移動",
242
+ "hintSelect": "選択",
243
+ "hintClose": "閉じる"
237
244
  }
238
245
  },
239
246
  "dataDisplay": {
@@ -19,7 +19,9 @@
19
19
  "dataEntry": {
20
20
  "calendar": {
21
21
  "today": "Hôm nay",
22
- "close": "Đóng"
22
+ "close": "Đóng",
23
+ "nav": "Điều hướng {label}",
24
+ "name": "Lịch"
23
25
  },
24
26
  "datePicker": {
25
27
  "placeholder": "Chọn ngày",
@@ -234,6 +236,11 @@
234
236
  "deniedDescription": "Hãy đề nghị quản trị viên cấp quyền truy cập.",
235
237
  "loading": "Đang tải vai trò…",
236
238
  "noDetail": "Chọn một vai trò để xem chi tiết"
239
+ },
240
+ "orgSwitcher": {
241
+ "hintMove": "Di chuyển",
242
+ "hintSelect": "Chọn",
243
+ "hintClose": "Đóng"
237
244
  }
238
245
  },
239
246
  "dataDisplay": {
@@ -3,6 +3,25 @@ import vi from "./messages/vi.json" with { type: "json" };
3
3
  export type Messages = typeof vi;
4
4
  export declare const MESSAGE_CATALOG: Record<AppLocale, Record<string, unknown>>;
5
5
  export type MessageKey = string;
6
+ /**
7
+ * ADD AN APPLICATION'S OWN STRINGS TO THE LIBRARY'S CATALOG.
8
+ *
9
+ * The catalog used to be closed: `MESSAGE_CATALOG` was exported but there was no way to extend it,
10
+ * so an application had to run a SECOND translation system beside this one — two lookups, two
11
+ * fallback chains, and two places a missing key can hide. That is a real cost for a product whose
12
+ * pages mix library chrome and application copy in the same sentence.
13
+ *
14
+ * MERGES, never replaces: a partial tree is layered onto what is already there, so registering
15
+ * `{ myApp: { title: "…" } }` cannot delete `dataEntry.calendar.today`. Registering the same key
16
+ * twice is last-write-wins, which is what a hot reload needs and what a second call with corrected
17
+ * copy expects.
18
+ *
19
+ * A LIBRARY KEY CANNOT BE SILENTLY REPLACED. Overwriting `dataEntry.*` or any other namespace this
20
+ * package ships would let one application change what a shared component says — invisibly, from a
21
+ * distance, and only in the build where that call ran. Reach for the component's own labels prop
22
+ * instead; every string this library renders has one.
23
+ */
24
+ export declare function registerMessages(locale: AppLocale, messages: Record<string, unknown>): void;
6
25
  export type TranslateParams = Record<string, string | number>;
7
26
  /** Resolve a dot-path message with fallback locale chain, CLDR plurals, and locale number formatting. */
8
27
  export declare function translate(locale: AppLocale, fallbackLocale: AppLocale, key: MessageKey, params?: TranslateParams): string;
@@ -6,6 +6,29 @@ const MESSAGE_CATALOG = {
6
6
  en,
7
7
  ja
8
8
  };
9
+ const LIBRARY_NAMESPACES = new Set(
10
+ Object.values(MESSAGE_CATALOG).flatMap((messages) => Object.keys(messages))
11
+ );
12
+ function registerMessages(locale, messages) {
13
+ const reserved = [...LIBRARY_NAMESPACES].filter((key) => Object.hasOwn(messages, key));
14
+ if (reserved.length > 0) {
15
+ throw new Error(
16
+ `@godxjp/ui i18n: [${reserved.join(", ")}] ${reserved.length === 1 ? "is a" : "are"} reserved top-level namespace(s) owned by the library. Register your strings under a namespace of your own, and change what a component says through its \`labels\` prop.`
17
+ );
18
+ }
19
+ MESSAGE_CATALOG[locale] = mergeDeep(MESSAGE_CATALOG[locale] ?? {}, messages);
20
+ }
21
+ function mergeDeep(target, source) {
22
+ const merged = { ...target };
23
+ for (const [key, value] of Object.entries(source)) {
24
+ const existing = merged[key];
25
+ merged[key] = isPlainObject(existing) && isPlainObject(value) ? mergeDeep(existing, value) : value;
26
+ }
27
+ return merged;
28
+ }
29
+ function isPlainObject(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
9
32
  function getNested(obj, path) {
10
33
  const parts = path.split(".");
11
34
  let current = obj;
@@ -75,6 +98,7 @@ export {
75
98
  MESSAGE_CATALOG,
76
99
  getSyncedFallbackLocale,
77
100
  getSyncedLocale,
101
+ registerMessages,
78
102
  resetI18nLocale,
79
103
  syncI18nLocale,
80
104
  translate,
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ export interface OverlayPortalProviderProps {
3
+ /**
4
+ * The element overlays are rendered into. Pass the shadow root, or any element inside it.
5
+ * `undefined` restores the default, so a subtree can opt back out.
6
+ */
7
+ container: Element | undefined;
8
+ children: React.ReactNode;
9
+ }
10
+ export declare function OverlayPortalProvider({ container, children }: OverlayPortalProviderProps): React.JSX.Element;
11
+ /**
12
+ * The container this overlay should render into. An explicit prop wins, so one panel can be placed
13
+ * elsewhere without unmounting the provider around it.
14
+ *
15
+ * Not exported from the public barrel: this is a contract between the provider and the overlays
16
+ * this library ships, not a knob for consumers — a consumer that needs the value has a provider.
17
+ */
18
+ export declare function useOverlayPortalContainer(override?: Element): Element | undefined;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { enableShadowDOM } from "react-stately/private/flags/flags";
5
+ const OverlayPortalContext = React.createContext(void 0);
6
+ enableShadowDOM();
7
+ function OverlayPortalProvider({ container, children }) {
8
+ return /* @__PURE__ */ jsx(OverlayPortalContext.Provider, { value: container, children });
9
+ }
10
+ function useOverlayPortalContainer(override) {
11
+ const fromContext = React.useContext(OverlayPortalContext);
12
+ return override ?? fromContext;
13
+ }
14
+ export {
15
+ OverlayPortalProvider,
16
+ useOverlayPortalContainer
17
+ };
@@ -3,6 +3,7 @@ import type { Locale } from "date-fns";
3
3
  import type { DayPickerProps } from "react-day-picker";
4
4
  import type { AppLocale, AppRequestHeaders, AppTimeFormat, AppTimezone, AppTimezoneDefault, AppDateFormat } from "../../app/types.js";
5
5
  import type { AppBrand, AppDensity, AppFontSize, AppTheme } from "../../app/theme-axes.js";
6
+ import type { AppPreferenceAxis } from "../../app/storage.js";
6
7
  import type { AppSettingPickerAppearanceProp, AppSettingToggleAppearanceProp, ChildrenProp, ClassNameProp, DisabledProp, IdProp, NameProp, OnValueChangeProp, ValueProp } from "../vocabulary/index.js";
7
8
  /** @see AppProvider */
8
9
  export type AppProviderProp = {
@@ -26,8 +27,18 @@ export type AppProviderProp = {
26
27
  timezoneOptions?: readonly AppTimezone[];
27
28
  /** localStorage key. Default: `godxjp.app`. */
28
29
  storageKey?: string;
29
- /** Persist user choices. Default: true. */
30
- persist?: boolean;
30
+ /**
31
+ * Which viewer preferences survive a reload. `true` (default) every axis, `false` none, or a
32
+ * LIST of axes — `["theme", "density", "fontSize"]`.
33
+ *
34
+ * The list exists because the axes do not share an owner. `theme` / `brand` / `density` /
35
+ * `fontSize` / `scaling` are the VIEWER's and belong in this browser. `locale` / `timezone` /
36
+ * `timeFormat` / `dateFormat` are frequently the SERVER's, resolved per request from a cookie,
37
+ * an account row or a header — and a stored copy then WINS over the value the server just sent,
38
+ * because storage is read after the props. Faced with one all-or-nothing flag, that consumer
39
+ * sets `persist={false}` and loses the viewer's theme along with it; naming the axes keeps both.
40
+ */
41
+ persist?: boolean | readonly AppPreferenceAxis[];
31
42
  /**
32
43
  * Initial theme choice. `"light"` / `"dark"` are written straight to `<html data-theme>`;
33
44
  * `"system"` defers to `prefers-color-scheme` and is re-resolved whenever the OS changes.
@@ -89,6 +89,19 @@ export type ButtonProp = React.ButtonHTMLAttributes<HTMLButtonElement> & {
89
89
  /** Corner shape — `default` (control radius) · `pill` (fully rounded) · `sharp` (square). */
90
90
  shape?: ShapeProp;
91
91
  fullWidth?: boolean;
92
+ /**
93
+ * Take the space the siblings leave, and let a long label ELLIPSE instead of widening the row —
94
+ * the same axis `Flex` calls `fill`, for the same reason.
95
+ *
96
+ * Button ships `flex-shrink: 0`, right almost everywhere and wrong in a constrained bar: an
97
+ * account menu holding an avatar plus a person's name keeps its full width while the cluster
98
+ * clips it, so a keyboard user tabs to a control they cannot see (SC 2.4.7). Until this axis
99
+ * existed the only move was `className="min-w-0 flex-1"`, which ui-audit blocks — and which this
100
+ * package's own Topbar guidance recommended, so the docs prescribed the utility the audit forbids.
101
+ *
102
+ * Sets `flex: 1 1 auto` and `min-inline-size: 0`; pair it with a `<Text truncate>` label.
103
+ */
104
+ fill?: boolean;
92
105
  /** Allow a text button to grow vertically for multi-line labels. */
93
106
  wrap?: boolean;
94
107
  /** Logical content alignment, especially for full-width collection actions. */
@@ -336,6 +336,30 @@ export type AppShellProp = {
336
336
  * to be tellable apart by name; the shell always supplies both defaults rather than requiring
337
337
  * this prop, so the two columns of equal rank behave the same way.
338
338
  */
339
+ /**
340
+ * WHICH EDGE the rail sits on. `start` (default) and `end` are the INLINE edges — logical, so an
341
+ * RTL document mirrors them without a `[dir]` rule; `top` and `bottom` are the block edges, where
342
+ * the rail becomes a full-measure horizontal strip and the shell grows a ROW instead of a column.
343
+ *
344
+ * The scope contract does not move with it: wherever it sits, the rail is PLATFORM scope. The
345
+ * edge is a presentation choice — a docked column reads as permanent chrome (Slack), a bottom
346
+ * strip reads as the phone/tab-bar shape, a top strip as a platform band above the app's own bar.
347
+ * Thickness follows the orientation: `--app-shell-nav-rail-width` as a column,
348
+ * `--app-shell-nav-rail-height` as a strip.
349
+ *
350
+ * Collapsing the sidebar folds the sidebar track only, at every position.
351
+ */
352
+ navRailPosition?: "start" | "end" | "top" | "bottom";
353
+ /**
354
+ * Rail content pinned to its FAR end — the counterpart of `Sidebar`'s `footer`, and the tray end
355
+ * of a taskbar: settings, appearance, the account glyph. It follows the orientation, so it is the
356
+ * bottom of a column and the inline-end of a strip, and it stays put while `navRail` scrolls.
357
+ *
358
+ * A slot rather than "whatever you put last", because pinning it needs an auto margin on the
359
+ * right axis — geometry that would otherwise land in consumer CSS, which this library does not
360
+ * accept. Ignored when `navRail` is not passed: there is no rail to pin anything to.
361
+ */
362
+ navRailEnd?: ReactNode;
339
363
  navRailLabel?: string;
340
364
  /**
341
365
  * Navigation shown in the mobile drawer at the DXS 900px breakpoint, where the docked sidebar is
@@ -398,6 +422,22 @@ export type AuthShellProp = {
398
422
  * flow geometry.
399
423
  */
400
424
  measure?: "default" | "wide";
425
+ /**
426
+ * Block-axis placement of the auth column, ORTHOGONAL to `preset` the way `variant` is: a preset
427
+ * owns the page MEASURE (card width, inline gutters, section rhythm), `align` owns where that
428
+ * column sits vertically.
429
+ *
430
+ * Omit it to keep the preset's own choice — `"login"` and `"registration"` anchor so a
431
+ * requester/identity line that wraps to two lines cannot move the card, every other preset
432
+ * centres. Pass `"center"` for a vertically centred column (the block-start inset collapses to
433
+ * the preset's block-end one, desktop and mobile, so the padding is symmetric) or `"anchored"`
434
+ * for a top-anchored one. This replaces re-declaring a preset's offset tokens from consumer CSS.
435
+ *
436
+ * HAZARD on a tall flow: a vertically centred tall card overflows ABOVE the scroll origin on a
437
+ * short viewport, putting its first field out of reach. That is why `"registration"` anchors by
438
+ * default; `"center"` is legal there but is the caller's judgement.
439
+ */
440
+ align?: "anchored" | "center";
401
441
  /**
402
442
  * Vertical density scoped to auth-card descendants. The canonical variant defaults to
403
443
  * `"compact"`; the default variant defaults to `"comfortable"`.
@@ -988,6 +1028,11 @@ export type AppLauncherProp = {
988
1028
  * Grid column count. Omit it and the panel keeps the stylesheet's own `--app-launcher-columns`
989
1029
  * (3, the Google-launcher shape, declared on `.ui-app-launcher-panel`): the default is where a
990
1030
  * theme can reach it, and this prop is the per-instance override written inline on top.
1031
+ *
1032
+ * `responsive="fullscreen"` steps that DEFAULT up with the surface — 3 · 4 · 5 · 6 on the house
1033
+ * container ladder — because a fixed three columns on a full viewport is three columns of tiles
1034
+ * and a screen of nothing. Passing `columns` still wins everywhere: an inline custom property
1035
+ * beats every stylesheet rule, so a stated count is a stated count on both surfaces.
991
1036
  */
992
1037
  columns?: number;
993
1038
  /**
@@ -1008,11 +1053,42 @@ export type AppLauncherProp = {
1008
1053
  * responsive="auto"`, resolved through the shared `useSheetResponsiveMode()` hook, so a service
1009
1054
  * moves the drawer line once for every overlay instead of per component.
1010
1055
  *
1011
- * No `"dialog"` here, unlike `OrgSwitcher`: a launcher grid is a jump table, and a modal that
1012
- * takes over the screen to offer nine links is heavier than the errand. Switching ORGANIZATION
1013
- * re-scopes everything on screen and earns the interruption; opening an app does not.
1056
+ * `"fullscreen"` is the LAUNCHPAD: one full-viewport surface, the page behind it blurred, the
1057
+ * grid floating on that ground at tile size the macOS Launchpad / Windows Start shape. It is
1058
+ * pinned at every width, because a start surface that becomes a popover on a wide screen is two
1059
+ * different products.
1060
+ *
1061
+ * WHEN IT IS RIGHT, AND WHEN IT IS NOT. This prop used to say a modal was always wrong here:
1062
+ * "a launcher grid is a jump table, and a modal that takes over the screen to offer nine links
1063
+ * is heavier than the errand." That reasoning is sound for a launcher in ONE application's
1064
+ * topbar, where the grid is a shortcut away from the work on screen and the work should stay
1065
+ * visible. It does not hold for a PLATFORM start bar — a strip that is present in every service,
1066
+ * whose launcher is the primary way to move between products rather than a shortcut. There the
1067
+ * grid IS the errand, the page behind it is the thing being left, and the interruption is the
1068
+ * point. Keep `"auto"` for a topbar launcher; reach for `"fullscreen"` for a dock.
1069
+ */
1070
+ responsive?: "auto" | "popover" | "sheet" | "fullscreen";
1071
+ /**
1072
+ * The BOX the trigger takes — the same split `AppSettingToggle` draws, and for the same reason.
1073
+ * `bar` (default) is a `TopbarItem`: a cell as tall as the bar, whose hover is the bar's own
1074
+ * surface. `icon` is a square ghost `Button`, for chrome that is NOT a bar — a nav rail, a card
1075
+ * header, a toolbar. A `TopbarItem` outside a bar has nothing to bleed to: it stretches to a
1076
+ * container that never set a band height, and its squared corners and full-bleed hover read as a
1077
+ * broken cell. The panel, the grid and the responsive contract are identical either way.
1078
+ */
1079
+ appearance?: "bar" | "icon";
1080
+ /**
1081
+ * Which side of the trigger the panel opens on, and how it aligns to it. Both default from
1082
+ * `appearance` — a bar drops the grid below and aligns to the bar's end; anything else opens to
1083
+ * the inline-end aligned to the trigger's start.
1084
+ *
1085
+ * State them when the chrome can be RE-DOCKED. `appearance` says the trigger is not in a bar; it
1086
+ * cannot say which way is out, and a rail pinned to the top edge still opens downward. Measured
1087
+ * without this: a launcher in a top strip opened sideways and left the panel 440px from the
1088
+ * trigger it belonged to.
1014
1089
  */
1015
- responsive?: "auto" | "popover" | "sheet";
1090
+ side?: "top" | "right" | "bottom" | "left";
1091
+ align?: "start" | "center" | "end";
1016
1092
  open?: boolean;
1017
1093
  onOpenChange?: (open: boolean) => void;
1018
1094
  className?: ClassNameProp;
@@ -1026,7 +1102,21 @@ export type SidebarProp = {
1026
1102
  sections?: SidebarSectionProp[];
1027
1103
  product?: SidebarProductProp;
1028
1104
  onProductClick?: () => void;
1029
- brand?: ReactNode;
1105
+ /**
1106
+ * Header slot ABOVE the navigation, replacing `product`.
1107
+ *
1108
+ * Pass a FUNCTION to follow the EFFECTIVE collapsed state. A plain node cannot: `AppShell` hands
1109
+ * the same Sidebar to the drawer and the drawer un-collapses the rows (see `NavSurface`), so a
1110
+ * node built from the consumer's own `collapsed` boolean renders a glyph-only lockup inside a
1111
+ * full-width drawer. The escape hatch consumers reach for is a SECOND hand-built `Sidebar` in
1112
+ * `AppShell.mobileNav` — and that override is precisely what switches off `railInDrawer`, so the
1113
+ * `navRail` silently stops reaching mobile. The function is called with the surface-effective
1114
+ * value, which removes the reason to build the second node at all.
1115
+ *
1116
+ * SCOPE: this is the APP's brand lockup. A PLATFORM switch (which organization, which app) does
1117
+ * not belong here — see AppShell's `navRail` for where it goes and why.
1118
+ */
1119
+ brand?: ReactNode | ((collapsed: boolean) => ReactNode);
1030
1120
  collapsed?: boolean;
1031
1121
  children?: ChildrenProp;
1032
1122
  /**
@@ -458,6 +458,7 @@
458
458
  display: flex;
459
459
  min-width: 0;
460
460
  align-items: center;
461
+ flex-wrap: wrap;
461
462
  gap: var(--card-service-launcher-space-gap);
462
463
  }
463
464
 
@@ -483,9 +484,17 @@
483
484
  height: var(--card-service-launcher-icon-glyph-size);
484
485
  }
485
486
 
487
+ [data-slot="service-launcher-identity"] {
488
+ display: flex;
489
+ min-width: 0;
490
+ flex: 1 1 auto;
491
+ align-items: center;
492
+ gap: var(--card-service-launcher-space-gap);
493
+ }
494
+
486
495
  [data-slot="service-launcher-title"] {
487
496
  min-width: 0;
488
- flex: 1;
497
+ flex: 1 1 auto;
489
498
  overflow-wrap: anywhere;
490
499
  font-size: var(--card-title-font-size);
491
500
  font-weight: var(--card-title-font-weight);
@@ -113,7 +113,8 @@
113
113
  min-inline-size: 0;
114
114
  overflow: hidden;
115
115
  font-size: var(--chart-trend-tick-font-size);
116
- line-height: var(--line-height-tight);
116
+
117
+ line-height: var(--line-height-normal);
117
118
  color: hsl(var(--muted-foreground));
118
119
  text-align: center;
119
120
  text-overflow: ellipsis;