@godxjp/ui 18.15.4 → 19.1.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.
Files changed (154) hide show
  1. package/dist/app/app-provider.d.ts +1 -1
  2. package/dist/app/app-provider.js +3 -0
  3. package/dist/app/date-format-labels.d.ts +1 -1
  4. package/dist/app/time-format-labels.d.ts +1 -1
  5. package/dist/components/data-display/badge.d.ts +1 -1
  6. package/dist/components/data-display/card.d.ts +4 -13
  7. package/dist/components/data-display/card.js +2 -15
  8. package/dist/components/data-display/data-table.d.ts +3 -8
  9. package/dist/components/data-display/data-table.js +106 -68
  10. package/dist/components/data-display/descriptions.js +34 -5
  11. package/dist/components/data-display/empty-state.js +1 -1
  12. package/dist/components/data-display/permission-matrix.js +7 -7
  13. package/dist/components/data-display/popover.js +2 -9
  14. package/dist/components/data-display/qr-code.d.ts +1 -1
  15. package/dist/components/data-display/scroll-area.d.ts +3 -1
  16. package/dist/components/data-display/scroll-area.js +150 -16
  17. package/dist/components/data-display/table.js +12 -3
  18. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  19. package/dist/components/data-entry/calendar.js +15 -24
  20. package/dist/components/data-entry/cascader.js +70 -72
  21. package/dist/components/data-entry/checkbox-group.js +12 -5
  22. package/dist/components/data-entry/checkbox.js +30 -13
  23. package/dist/components/data-entry/command.d.ts +6 -6
  24. package/dist/components/data-entry/date-picker.js +34 -17
  25. package/dist/components/data-entry/date-range-picker.js +19 -13
  26. package/dist/components/data-entry/form-field.d.ts +1 -1
  27. package/dist/components/data-entry/form-field.js +22 -5
  28. package/dist/components/data-entry/index.d.ts +2 -1
  29. package/dist/components/data-entry/input.js +18 -22
  30. package/dist/components/data-entry/label.js +4 -2
  31. package/dist/components/data-entry/month-picker.js +20 -17
  32. package/dist/components/data-entry/month-range-picker.js +26 -19
  33. package/dist/components/data-entry/radio.js +25 -6
  34. package/dist/components/data-entry/search-select.d.ts +1 -1
  35. package/dist/components/data-entry/search-select.js +37 -31
  36. package/dist/components/data-entry/select.d.ts +8 -0
  37. package/dist/components/data-entry/select.js +68 -26
  38. package/dist/components/data-entry/switch.js +16 -2
  39. package/dist/components/data-entry/textarea.d.ts +56 -0
  40. package/dist/components/data-entry/textarea.js +110 -40
  41. package/dist/components/data-entry/time-picker.js +52 -52
  42. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  43. package/dist/components/data-entry/toggle.d.ts +1 -1
  44. package/dist/components/data-entry/transfer.js +14 -15
  45. package/dist/components/data-entry/tree-select.js +19 -23
  46. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  47. package/dist/components/data-entry/upload.d.ts +1 -1
  48. package/dist/components/data-entry/upload.js +46 -46
  49. package/dist/components/feedback/alert.js +7 -14
  50. package/dist/components/feedback/dialog.d.ts +23 -32
  51. package/dist/components/feedback/dialog.js +16 -13
  52. package/dist/components/feedback/sheet.d.ts +1 -1
  53. package/dist/components/feedback/sheet.js +14 -14
  54. package/dist/components/feedback/skeleton.js +13 -10
  55. package/dist/components/feedback/sonner.d.ts +1 -1
  56. package/dist/components/feedback/sonner.js +17 -8
  57. package/dist/components/feedback/tooltip.js +1 -1
  58. package/dist/components/general/activity.d.ts +44 -0
  59. package/dist/components/general/activity.js +65 -0
  60. package/dist/components/general/button.d.ts +2 -2
  61. package/dist/components/general/button.js +11 -24
  62. package/dist/components/general/index.d.ts +2 -0
  63. package/dist/components/general/index.js +2 -0
  64. package/dist/components/layout/app-shell.js +5 -3
  65. package/dist/components/layout/auth-divider.d.ts +9 -2
  66. package/dist/components/layout/auth-divider.js +7 -11
  67. package/dist/components/layout/index.d.ts +1 -0
  68. package/dist/components/layout/page-container.d.ts +1 -1
  69. package/dist/components/layout/page-container.js +32 -20
  70. package/dist/components/layout/resizable.d.ts +12 -12
  71. package/dist/components/layout/separator.d.ts +1 -0
  72. package/dist/components/layout/service-role-panel.js +47 -42
  73. package/dist/components/layout/sidebar.js +13 -1
  74. package/dist/components/layout/split-pane.d.ts +12 -1
  75. package/dist/components/layout/split-pane.js +13 -4
  76. package/dist/components/navigation/app-setting-picker.js +5 -14
  77. package/dist/components/navigation/dropdown-menu.js +10 -16
  78. package/dist/components/navigation/menubar.d.ts +3 -6
  79. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  80. package/dist/components/navigation/pagination-utils.js +27 -21
  81. package/dist/components/navigation/pagination.d.ts +4 -4
  82. package/dist/components/navigation/pagination.js +2 -2
  83. package/dist/components/navigation/steps.js +31 -67
  84. package/dist/components/navigation/tabs.js +17 -5
  85. package/dist/components/query/infinite-query-state.js +2 -2
  86. package/dist/components/ui/accordion.d.ts +1 -1
  87. package/dist/components/ui/avatar.d.ts +1 -1
  88. package/dist/components/ui/avatar.js +29 -10
  89. package/dist/components/ui/input-otp.d.ts +8 -6
  90. package/dist/components/ui/rating.js +1 -1
  91. package/dist/components/ui/separator.d.ts +27 -2
  92. package/dist/components/ui/separator.js +42 -11
  93. package/dist/components/ui/toggle-group.d.ts +15 -5
  94. package/dist/components/ui/toggle-group.js +43 -18
  95. package/dist/components/ui/toggle.d.ts +66 -5
  96. package/dist/components/ui/toggle.js +64 -9
  97. package/dist/i18n/messages/en.json +8 -0
  98. package/dist/i18n/messages/ja.json +8 -0
  99. package/dist/i18n/messages/vi.json +8 -0
  100. package/dist/lib/control-styles.d.ts +14 -0
  101. package/dist/lib/control-styles.js +2 -0
  102. package/dist/lib/field-a11y.d.ts +52 -0
  103. package/dist/lib/field-a11y.js +17 -2
  104. package/dist/props/components/app.prop.d.ts +15 -0
  105. package/dist/props/components/data-display.prop.d.ts +85 -3
  106. package/dist/props/components/data-entry.prop.d.ts +35 -2
  107. package/dist/props/components/general.prop.d.ts +52 -1
  108. package/dist/props/components/layout.prop.d.ts +137 -2
  109. package/dist/props/registry.d.ts +116 -5
  110. package/dist/props/registry.js +165 -4
  111. package/dist/props/vocabulary/index.d.ts +2 -2
  112. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  113. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  114. package/dist/styles/alert-layout.css +69 -3
  115. package/dist/styles/badge-layout.css +2 -2
  116. package/dist/styles/base.css +6 -0
  117. package/dist/styles/card-layout.css +25 -8
  118. package/dist/styles/control.css +1150 -29
  119. package/dist/styles/data-display-layout.css +185 -11
  120. package/dist/styles/data-entry-layout.css +352 -1
  121. package/dist/styles/density.css +2 -2
  122. package/dist/styles/dialog-layout.css +203 -4
  123. package/dist/styles/focus-ring.css +155 -0
  124. package/dist/styles/form-layout.css +50 -5
  125. package/dist/styles/index.css +1 -0
  126. package/dist/styles/layout.css +412 -23
  127. package/dist/styles/motion.css +178 -0
  128. package/dist/styles/navigation-layout.css +398 -22
  129. package/dist/styles/shell-layout.css +137 -39
  130. package/dist/styles/table-layout.css +153 -22
  131. package/dist/styles/toggle.css +59 -0
  132. package/dist/tokens/base.css +5 -0
  133. package/dist/tokens/components/activity.css +64 -0
  134. package/dist/tokens/components/badge.css +7 -0
  135. package/dist/tokens/components/card.css +14 -10
  136. package/dist/tokens/components/chart.css +1 -1
  137. package/dist/tokens/components/control.css +358 -8
  138. package/dist/tokens/components/data-display.css +75 -0
  139. package/dist/tokens/components/data-entry.css +25 -0
  140. package/dist/tokens/components/descriptions.css +22 -0
  141. package/dist/tokens/components/feedback.css +143 -4
  142. package/dist/tokens/components/form.css +16 -0
  143. package/dist/tokens/components/legal-document.css +5 -1
  144. package/dist/tokens/components/navigation.css +109 -0
  145. package/dist/tokens/components/scroll-area.css +21 -0
  146. package/dist/tokens/components/separator.css +52 -0
  147. package/dist/tokens/components/shell.css +132 -20
  148. package/dist/tokens/components/table.css +52 -3
  149. package/dist/tokens/components/toggle.css +82 -0
  150. package/dist/tokens/components/upload.css +107 -0
  151. package/dist/tokens/foundation.css +171 -10
  152. package/dist/tokens/semantic/layout.css +120 -1
  153. package/package.json +85 -68
  154. package/scripts/ui-audit.mjs +51 -2
@@ -6,20 +6,11 @@ import { cva } from "class-variance-authority";
6
6
  import { Loader2 } from "lucide-react";
7
7
  import { cn } from "../../lib/utils.js";
8
8
  import { useTranslation } from "../../i18n/use-translation.js";
9
- const buttonCountClass = {
10
- default: "bg-primary-foreground/15",
11
- destructive: "bg-destructive-foreground/15",
12
- secondary: "bg-secondary-foreground/15",
13
- outline: "bg-foreground/8 text-muted-foreground",
14
- dashed: "bg-foreground/8 text-muted-foreground",
15
- ghost: "bg-foreground/8 text-muted-foreground",
16
- link: "bg-foreground/8 text-muted-foreground"
17
- };
18
9
  const buttonVariants = cva("ui-button", {
19
10
  variants: {
20
11
  variant: {
21
12
  default: "ui-button--default bg-primary text-primary-foreground hover:bg-primary/90",
22
- destructive: "ui-button--destructive bg-destructive text-destructive-foreground focus-visible:ring-destructive/20",
13
+ destructive: "ui-button--destructive bg-destructive text-destructive-foreground",
23
14
  outline: "ui-button--outline border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
24
15
  dashed: "ui-button--dashed border border-dashed bg-background hover:bg-accent hover:text-accent-foreground",
25
16
  secondary: "ui-button--secondary bg-secondary text-secondary-foreground hover:bg-secondary/80",
@@ -27,13 +18,16 @@ const buttonVariants = cva("ui-button", {
27
18
  link: "ui-button--link text-primary underline-offset-4 hover:underline"
28
19
  },
29
20
  size: {
30
- default: "ui-button--default-size py-2 has-[>svg]:px-3",
31
- md: "ui-button--default-size py-2 has-[>svg]:px-3",
32
- xs: "h-[calc(var(--control-height)-0.75rem)] gap-1 px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
33
- sm: "ui-button--sm gap-1.5 has-[>svg]:px-2.5",
34
- lg: "ui-button--lg has-[>svg]:px-4",
21
+ default: "ui-button--default-size",
22
+ md: "ui-button--default-size",
23
+ // The glyph rules stay UTILITIES, not a components-layer rule: Tailwind v4 orders
24
+ // utilities after components, so only a utility can out-rank a child's own `size-*`.
25
+ // They read the token, so the value is still themeable.
26
+ xs: "ui-button--xs [&_svg:not([class*='size-'])]:size-[var(--button-xs-icon-size)]",
27
+ sm: "ui-button--sm",
28
+ lg: "ui-button--lg",
35
29
  icon: "ui-button--icon",
36
- "icon-xs": "ui-button--icon-xs [&_svg]:size-3 [&_svg]:shrink-0",
30
+ "icon-xs": "ui-button--icon-xs [&_svg]:size-[var(--button-xs-icon-size)] [&_svg]:shrink-0",
37
31
  "icon-sm": "ui-button--icon-sm",
38
32
  "icon-lg": "ui-button--icon-lg"
39
33
  },
@@ -79,10 +73,7 @@ const Button = React.forwardRef(
79
73
  "span",
80
74
  {
81
75
  "data-slot": "button-count",
82
- className: cn(
83
- "inline-flex min-w-4 items-center justify-center rounded-[var(--radius-pill)] px-1 text-xs leading-none tabular-nums",
84
- buttonCountClass[variant ?? "default"]
85
- ),
76
+ className: "ui-button-count",
86
77
  children: countLabel
87
78
  }
88
79
  ) : null;
@@ -99,11 +90,7 @@ const Button = React.forwardRef(
99
90
  disabled: isLoading || disabled,
100
91
  type: asChild ? void 0 : type ?? "button",
101
92
  className: cn(
102
- "inline-flex shrink-0 items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-all outline-none",
103
93
  fullWidth && "w-full",
104
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
105
- "disabled:pointer-events-none disabled:opacity-50",
106
- "data-[loading]:pointer-events-none",
107
94
  "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
108
95
  "[&_svg]:pointer-events-none [&_svg]:shrink-0",
109
96
  buttonVariants({ variant, size, shape, className })
@@ -6,3 +6,5 @@ export { Logo } from "./logo.js";
6
6
  export type { LogoMark, LogoProps, LogoSize, LogoTone } from "./logo.js";
7
7
  export { Reveal } from "./reveal.js";
8
8
  export type { RevealProp, RevealProps } from "./reveal.js";
9
+ export { Activity } from "./activity.js";
10
+ export type { ActivityProp, ActivityProps } from "./activity.js";
@@ -2,7 +2,9 @@ import { Button, buttonVariants } from "./button.js";
2
2
  import { Text, Heading } from "./typography.js";
3
3
  import { Logo } from "./logo.js";
4
4
  import { Reveal } from "./reveal.js";
5
+ import { Activity } from "./activity.js";
5
6
  export {
7
+ Activity,
6
8
  Button,
7
9
  Heading,
8
10
  Logo,
@@ -35,6 +35,7 @@ function AppShell({
35
35
  setDrawerOpen(false);
36
36
  }
37
37
  };
38
+ const hasTopbarContent = topbar !== void 0 || topbarLeft !== void 0 || topbarRight !== void 0 || logo !== void 0;
38
39
  const resolvedTopbar = topbar !== void 0 ? topbar : /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
39
40
  logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: logo }),
40
41
  topbarLeft !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-left", children: topbarLeft }),
@@ -42,7 +43,7 @@ function AppShell({
42
43
  topbarRight !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-right", children: topbarRight })
43
44
  ] });
44
45
  const rail = /* @__PURE__ */ jsx("aside", { className: "app-sidebar", "aria-label": t("layout.appShell.sidebarLabel"), children: sidebar });
45
- const bar = /* @__PURE__ */ jsxs("header", { className: "app-topbar ui-scale-fixed", "aria-label": t("layout.appShell.headerLabel"), children: [
46
+ const bar = !hasTopbarContent && !hasDrawer ? null : /* @__PURE__ */ jsxs("header", { className: "app-topbar ui-scale-fixed", "aria-label": t("layout.appShell.headerLabel"), children: [
46
47
  hasDrawer && /* @__PURE__ */ jsxs(Sheet, { open: drawerOpen, onOpenChange: setDrawerOpen, children: [
47
48
  /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
48
49
  Button,
@@ -53,7 +54,7 @@ function AppShell({
53
54
  className: "app-mobile-nav-trigger hidden max-[900px]:inline-flex",
54
55
  "aria-label": t("layout.appShell.openNav"),
55
56
  "aria-haspopup": "dialog",
56
- children: /* @__PURE__ */ jsx(Menu, { className: "size-5", "aria-hidden": "true" })
57
+ children: /* @__PURE__ */ jsx(Menu, { className: "size-[var(--app-shell-mobile-nav-icon-size)]", "aria-hidden": "true" })
57
58
  }
58
59
  ) }),
59
60
  /* @__PURE__ */ jsxs(
@@ -77,7 +78,7 @@ function AppShell({
77
78
  }
78
79
  )
79
80
  ] }),
80
- resolvedTopbar
81
+ hasTopbarContent && resolvedTopbar
81
82
  ] });
82
83
  return /* @__PURE__ */ jsxs(
83
84
  "div",
@@ -85,6 +86,7 @@ function AppShell({
85
86
  className: "app-root",
86
87
  "data-collapsed": sidebarCollapsed ? "true" : void 0,
87
88
  "data-responsive-navigation": responsiveNavigation,
89
+ "data-topbar": hasTopbarContent ? void 0 : "none",
88
90
  "data-topbar-span": topbarSpan === "full" ? "full" : void 0,
89
91
  children: [
90
92
  topbarSpan === "full" ? bar : rail,
@@ -1,7 +1,14 @@
1
1
  import type { AuthDividerProp } from "../../props/components/layout.prop.js";
2
2
  export type { AuthDividerProp, AuthDividerProp as AuthDividerProps, } from "../../props/components/layout.prop.js";
3
3
  /**
4
- * AuthDivider — a centred, localized conjunction between two equal separator rules. The grid
5
- * composition keeps the label optically and geometrically centred regardless of translation.
4
+ * AuthDivider — a centred, localized conjunction between two equal separator rules.
5
+ *
6
+ * Since gh#308 this is a thin PRESET over `Separator label`, not a parallel implementation: the
7
+ * grid, the accessible-name contract and the rule geometry all live on the primitive, and
8
+ * `.ui-auth-divider` only re-points Separator's `--separator-*` knobs at the `--auth-shell-divider-*`
9
+ * layer. That keeps #263's canonical login geometry byte-for-byte identical while breaking the
10
+ * coupling the issue reported — a service retuning its login divider no longer retunes every day
11
+ * divider in its message streams, and vice versa. New code that wants a labelled rule outside an
12
+ * auth form should use `<Separator label>` directly.
6
13
  */
7
14
  export declare function AuthDivider({ label, className }: AuthDividerProp): import("react").JSX.Element;
@@ -1,18 +1,14 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
2
3
  import { cn } from "../../lib/utils.js";
4
+ import { Separator } from "../ui/separator.js";
3
5
  function AuthDivider({ label, className }) {
4
- return /* @__PURE__ */ jsxs(
5
- "div",
6
+ return /* @__PURE__ */ jsx(
7
+ Separator,
6
8
  {
7
9
  "data-slot": "auth-divider",
8
- className: cn("ui-auth-divider", className),
9
- role: "separator",
10
- "aria-label": label,
11
- children: [
12
- /* @__PURE__ */ jsx("span", { className: "ui-auth-divider-rule", "aria-hidden": "true" }),
13
- /* @__PURE__ */ jsx("span", { className: "ui-auth-divider-label", "aria-hidden": "true", children: label }),
14
- /* @__PURE__ */ jsx("span", { className: "ui-auth-divider-rule", "aria-hidden": "true" })
15
- ]
10
+ label,
11
+ className: cn("ui-auth-divider", className)
16
12
  }
17
13
  );
18
14
  }
@@ -39,6 +39,7 @@ export type { MasterDetailProps } from "./master-detail.js";
39
39
  export { SplitPane } from "./split-pane.js";
40
40
  export type { SplitPaneProps } from "./split-pane.js";
41
41
  export { Separator } from "./separator.js";
42
+ export type { SeparatorProp, SeparatorProps } from "./separator.js";
42
43
  export { AspectRatio } from "./aspect-ratio.js";
43
44
  export { LegalDocumentShell } from "./legal-document-shell.js";
44
45
  export type { LegalDocumentSectionProp, LegalDocumentShellProp, LegalDocumentShellProps, } from "./legal-document-shell.js";
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
2
2
  export type { PageContainerProp, PageContainerProp as PageContainerProps, } 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, footer, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, density, variant, preset, headerLayout, measure, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react").JSX.Element;
5
+ declare function PageContainerRoot({ title, subtitle, status, extra, toolbar, footer, breadcrumb, breadcrumbLabel, breadcrumbAriaLabel, linkComponent: LinkComponent, headerLoading, 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
  };
@@ -38,6 +38,7 @@ function PageContainerRoot({
38
38
  subtitle,
39
39
  status,
40
40
  extra,
41
+ toolbar,
41
42
  footer,
42
43
  breadcrumb,
43
44
  breadcrumbLabel,
@@ -48,6 +49,7 @@ function PageContainerRoot({
48
49
  variant = "default",
49
50
  preset = "default",
50
51
  headerLayout = "stack",
52
+ headerScale = "document",
51
53
  measure = "default",
52
54
  stickyFooter = false,
53
55
  footerReveal = "always",
@@ -63,6 +65,7 @@ function PageContainerRoot({
63
65
  {
64
66
  "data-preset": preset,
65
67
  "data-measure": measure,
68
+ "data-header-scale": headerScale === "chrome" ? "chrome" : void 0,
66
69
  "data-revealed": revealed ? "true" : void 0,
67
70
  className: cn(
68
71
  "ui-page-container",
@@ -91,30 +94,38 @@ function PageContainerRoot({
91
94
  className: "ui-breadcrumb",
92
95
  children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
93
96
  const isLast = i === breadcrumb.length - 1;
94
- return /* @__PURE__ */ jsxs("li", { className: "ui-inline-xs", children: [
95
- item.to && !isLast ? /* @__PURE__ */ jsx(
96
- LinkComponent,
97
- {
98
- href: item.to,
99
- to: item.to,
100
- className: "hover:text-foreground hover:underline",
101
- children: item.label
102
- }
103
- ) : /* @__PURE__ */ jsx(
104
- "span",
105
- {
106
- className: isLast ? "text-foreground" : "",
107
- "aria-current": isLast ? "page" : void 0,
108
- children: item.label
109
- }
110
- ),
111
- !isLast && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3", "aria-hidden": "true" })
112
- ] }, i);
97
+ return (
98
+ // The package's own breadcrumb item hook (gh#319): same
99
+ // `--space-inline-xs` gap the previous `.ui-inline-xs` gave, and it brings
100
+ // `.ui-breadcrumb-item svg { 0.75rem }` with it, so the separator glyph no
101
+ // longer carries a `size-3` literal. It also drops `.ui-inline-xs`'s
102
+ // `flex-wrap: wrap` — a crumb must not break between its label and its
103
+ // chevron; the LIST wraps instead, exactly as <Breadcrumb> already behaves.
104
+ /* @__PURE__ */ jsxs("li", { className: "ui-breadcrumb-item", children: [
105
+ item.to && !isLast ? /* @__PURE__ */ jsx(
106
+ LinkComponent,
107
+ {
108
+ href: item.to,
109
+ to: item.to,
110
+ className: "hover:text-foreground hover:underline",
111
+ children: item.label
112
+ }
113
+ ) : /* @__PURE__ */ jsx(
114
+ "span",
115
+ {
116
+ className: isLast ? "text-foreground" : "",
117
+ "aria-current": isLast ? "page" : void 0,
118
+ children: item.label
119
+ }
120
+ ),
121
+ !isLast && /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
122
+ ] }, i)
123
+ );
113
124
  }) })
114
125
  }
115
126
  ),
116
127
  /* @__PURE__ */ jsxs("div", { className: "ui-page-header-row", children: [
117
- /* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading min-w-0", children: [
128
+ /* @__PURE__ */ jsxs("div", { className: "ui-page-header-heading", children: [
118
129
  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: [
119
130
  /* @__PURE__ */ jsx("h1", { className: "ui-page-title", children: title }),
120
131
  /* @__PURE__ */ jsx("div", { className: "ui-page-header-status", children: status })
@@ -136,6 +147,7 @@ function PageContainerRoot({
136
147
  ]
137
148
  }
138
149
  ),
150
+ toolbar != null && /* @__PURE__ */ jsx("div", { className: "ui-page-toolbar", children: toolbar }),
139
151
  children != null && /* @__PURE__ */ jsx("div", { className: "ui-page-body", children }),
140
152
  footer && /* @__PURE__ */ jsx("footer", { className: "ui-page-footer", children: footer })
141
153
  ]
@@ -1,14 +1,14 @@
1
1
  import * as React from "react";
2
2
  import * as ResizablePrimitive from "react-resizable-panels";
3
- export declare const ResizablePanelGroup: {
4
- ({ className, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Group>): React.JSX.Element;
5
- displayName: string;
6
- };
7
- export declare const ResizablePanel: {
8
- ({ className, elementRef, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Panel>): React.JSX.Element;
9
- displayName: string;
10
- };
11
- export declare const ResizableHandle: {
12
- ({ className, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Separator>): React.JSX.Element;
13
- displayName: string;
14
- };
3
+ export declare function ResizablePanelGroup({ className, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Group>): React.JSX.Element;
4
+ export declare namespace ResizablePanelGroup {
5
+ var displayName: string;
6
+ }
7
+ export declare function ResizablePanel({ className, elementRef, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Panel>): React.JSX.Element;
8
+ export declare namespace ResizablePanel {
9
+ var displayName: string;
10
+ }
11
+ export declare function ResizableHandle({ className, ...props }: React.ComponentPropsWithoutRef<typeof ResizablePrimitive.Separator>): React.JSX.Element;
12
+ export declare namespace ResizableHandle {
13
+ var displayName: string;
14
+ }
@@ -1 +1,2 @@
1
1
  export { Separator } from "../ui/separator.js";
2
+ export type { SeparatorProp, SeparatorProps } from "../ui/separator.js";
@@ -132,49 +132,54 @@ const ServiceRolePanel = React.forwardRef(
132
132
  masterLabel: masterLabel ?? t("layout.serviceRolePanel.rolesLabel"),
133
133
  detailLabel: detailLabel ?? t("layout.serviceRolePanel.detailLabel"),
134
134
  detailId,
135
- master: /* @__PURE__ */ jsx("ul", { className: "flex list-none flex-col gap-1", "data-slot": "service-role-panel-list", children: roles.map((role) => {
135
+ master: /* @__PURE__ */ jsx("ul", { className: "ui-service-role-panel-list", "data-slot": "service-role-panel-list", children: roles.map((role) => {
136
136
  const selected = role.id === value;
137
- return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1", children: [
138
- /* @__PURE__ */ jsx(
139
- Button,
140
- {
141
- variant: selected ? "secondary" : "ghost",
142
- className: "h-auto flex-1 justify-start py-2 text-start",
143
- "aria-current": selected ? "true" : void 0,
144
- "aria-controls": detailId,
145
- onClick: () => select(role.id),
146
- children: /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col items-start leading-tight", children: [
147
- /* @__PURE__ */ jsxs("span", { className: "inline-flex max-w-full items-center gap-1.5", children: [
148
- /* @__PURE__ */ jsx(Text, { weight: "medium", className: "truncate", children: role.name }),
149
- role.locked && /* @__PURE__ */ jsxs(Badge, { tone: "default", variant: "outline", children: [
150
- /* @__PURE__ */ jsx(Lock, { "aria-hidden": "true" }),
151
- t("layout.serviceRolePanel.locked")
152
- ] })
153
- ] }),
154
- (role.description !== void 0 || role.memberCount !== void 0) && /* @__PURE__ */ jsx(Text, { size: "2xs", tone: "muted", className: "max-w-full truncate", children: [
155
- role.description,
156
- role.memberCount !== void 0 ? (
157
- // `{count}` is Intl.NumberFormat-grouped by `interpolate`,
158
- // pluralized by CLDR category no hand-built formatting.
159
- t("layout.serviceRolePanel.memberCount", {
160
- count: role.memberCount
161
- })
162
- ) : void 0
163
- ].filter((part) => part !== void 0).join(" \xB7 ") })
164
- ] })
165
- }
166
- ),
167
- deletable && !role.locked && /* @__PURE__ */ jsx(
168
- Button,
169
- {
170
- variant: "ghost",
171
- size: "icon-sm",
172
- "aria-label": t("layout.serviceRolePanel.deleteRole", { role: role.name }),
173
- onClick: () => setPendingDelete(role),
174
- children: /* @__PURE__ */ jsx(Trash2, { "aria-hidden": "true" })
175
- }
176
- )
177
- ] }, role.id);
137
+ return (
138
+ // NOT `.ui-inline-xs`: that helper adds `flex-wrap: wrap`, and this row holds a
139
+ // flex-1 select button next to an icon-only delete button that must never wrap
140
+ // onto its own line.
141
+ /* @__PURE__ */ jsxs("li", { className: "ui-service-role-panel-row", children: [
142
+ /* @__PURE__ */ jsx(
143
+ Button,
144
+ {
145
+ variant: selected ? "secondary" : "ghost",
146
+ className: "ui-service-role-panel-item h-auto flex-1 justify-start text-start",
147
+ "aria-current": selected ? "true" : void 0,
148
+ "aria-controls": detailId,
149
+ onClick: () => select(role.id),
150
+ children: /* @__PURE__ */ jsxs("span", { className: "ui-service-role-panel-item-body", children: [
151
+ /* @__PURE__ */ jsxs("span", { className: "ui-service-role-panel-item-title", children: [
152
+ /* @__PURE__ */ jsx(Text, { weight: "medium", className: "truncate", children: role.name }),
153
+ role.locked && /* @__PURE__ */ jsxs(Badge, { tone: "default", variant: "outline", children: [
154
+ /* @__PURE__ */ jsx(Lock, { "aria-hidden": "true" }),
155
+ t("layout.serviceRolePanel.locked")
156
+ ] })
157
+ ] }),
158
+ (role.description !== void 0 || role.memberCount !== void 0) && /* @__PURE__ */ jsx(Text, { size: "2xs", tone: "muted", className: "max-w-full truncate", children: [
159
+ role.description,
160
+ role.memberCount !== void 0 ? (
161
+ // `{count}` is Intl.NumberFormat-grouped by `interpolate`,
162
+ // pluralized by CLDR category — no hand-built formatting.
163
+ t("layout.serviceRolePanel.memberCount", {
164
+ count: role.memberCount
165
+ })
166
+ ) : void 0
167
+ ].filter((part) => part !== void 0).join(" \xB7 ") })
168
+ ] })
169
+ }
170
+ ),
171
+ deletable && !role.locked && /* @__PURE__ */ jsx(
172
+ Button,
173
+ {
174
+ variant: "ghost",
175
+ size: "icon-sm",
176
+ "aria-label": t("layout.serviceRolePanel.deleteRole", { role: role.name }),
177
+ onClick: () => setPendingDelete(role),
178
+ children: /* @__PURE__ */ jsx(Trash2, { "aria-hidden": "true" })
179
+ }
180
+ )
181
+ ] }, role.id)
182
+ );
178
183
  }) }),
179
184
  children: detail
180
185
  }
@@ -36,7 +36,19 @@ function SidebarRowContent({
36
36
  return /* @__PURE__ */ jsxs(Fragment, { children: [
37
37
  !sub ? /* @__PURE__ */ jsx(SidebarIcon, { icon: item.icon }) : null,
38
38
  /* @__PURE__ */ jsx("span", { className: "sb-label", children: item.label }),
39
- showBadge ? /* @__PURE__ */ jsx("span", { className: "sb-badge", children: item.badge }) : null
39
+ showBadge ? (
40
+ // `data-tone` is emitted ONLY for the emphasis tone (rule #44: present-when-on,
41
+ // absent-when-off), so a rail that never sets `badgeTone` renders the exact same node it
42
+ // always did and no consumer selector has to out-specify a marker meaning "unchanged".
43
+ /* @__PURE__ */ jsx(
44
+ "span",
45
+ {
46
+ className: "sb-badge",
47
+ "data-tone": item.badgeTone === "destructive" ? "destructive" : void 0,
48
+ children: item.badge
49
+ }
50
+ )
51
+ ) : null
40
52
  ] });
41
53
  }
42
54
  function SidebarItem({
@@ -1,7 +1,18 @@
1
1
  import type { ReactNode } from "react";
2
2
  export type SplitPaneProps = {
3
3
  children: ReactNode;
4
- aside: ReactNode;
4
+ /**
5
+ * Aside (trailing) panel content. Pass `null` to CLOSE the rail: no `<aside>` element is
6
+ * rendered, the grid drops to one full-width column and the gap goes with it.
7
+ *
8
+ * Closing this way — rather than dropping the whole `SplitPane` at the call site — is what
9
+ * keeps `children` MOUNTED. The three wrappers below are rendered unconditionally, so the
10
+ * main column sits at the same depth and the same slot in the React tree in both states and
11
+ * React reuses its DOM node instead of remounting it. A collapsible thread/detail panel that
12
+ * swapped `<SplitPane>{page}</SplitPane>` for a bare `{page}` changed the tree depth, which
13
+ * remounted the page and threw the reader to the bottom of a scrolled list.
14
+ */
15
+ aside: ReactNode | null;
5
16
  /**
6
17
  * How wide the rail is once the pane is wide enough to split: `sm` 20rem,
7
18
  * `md` 22rem, `lg` 30rem. `lg` is for rails that carry a panel rather than a
@@ -1,9 +1,18 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  function SplitPane({ children, aside, asideWidth = "md", asideLabel }) {
3
- return /* @__PURE__ */ jsx("div", { className: "ui-split-pane-scope", children: /* @__PURE__ */ jsxs("div", { className: "ui-split-pane", "data-aside-width": asideWidth, children: [
4
- /* @__PURE__ */ jsx("div", { className: "ui-split-pane-main", children }),
5
- /* @__PURE__ */ jsx("aside", { className: "ui-split-pane-aside", "aria-label": asideLabel, children: aside })
6
- ] }) });
3
+ const closed = aside === null || aside === void 0;
4
+ return /* @__PURE__ */ jsx("div", { className: "ui-split-pane-scope", children: /* @__PURE__ */ jsxs(
5
+ "div",
6
+ {
7
+ className: "ui-split-pane",
8
+ "data-aside-width": asideWidth,
9
+ "data-aside": closed ? "closed" : void 0,
10
+ children: [
11
+ /* @__PURE__ */ jsx("div", { className: "ui-split-pane-main", children }),
12
+ closed ? null : /* @__PURE__ */ jsx("aside", { className: "ui-split-pane-aside", "aria-label": asideLabel, children: aside })
13
+ ]
14
+ }
15
+ ) });
7
16
  }
8
17
  export {
9
18
  SplitPane
@@ -41,16 +41,6 @@ const ICON = {
41
41
  density: Rows3,
42
42
  fontSize: Type
43
43
  };
44
- const TRIGGER_WIDTH = {
45
- locale: "sm:w-40",
46
- timezone: "sm:w-56",
47
- dateFormat: "sm:w-44",
48
- timeFormat: "sm:w-44",
49
- theme: "sm:w-36",
50
- brand: "sm:w-44",
51
- density: "sm:w-40",
52
- fontSize: "sm:w-36"
53
- };
54
44
  const ARIA_KEY = {
55
45
  locale: "navigation.localePicker.ariaLabel",
56
46
  timezone: "navigation.timezonePicker.ariaLabel",
@@ -139,7 +129,7 @@ const AppSettingPicker = React.forwardRef(
139
129
  id,
140
130
  showIndicator: !iconOnly && !inline,
141
131
  className: cn(
142
- inline ? "ui-app-setting-picker-inline h-auto min-h-0 w-auto rounded-none border-0 bg-transparent p-0 shadow-none" : iconOnly ? (
132
+ inline ? "ui-app-setting-picker-inline" : iconOnly ? (
143
133
  // Structurally icon-only: drop the owned width + value spacing and square the box to
144
134
  // the density-aware --control-height tap target, centring the icon. Visually it
145
135
  // sits among ghost icon buttons (the topbar's sidebar-toggle/notifications/account
@@ -149,7 +139,7 @@ const AppSettingPicker = React.forwardRef(
149
139
  // (`data-[state=open]:border-ring`, from controlTriggerClass) and the
150
140
  // focus-visible ring are untouched, so keyboard and "is this open" affordance
151
141
  // still hold.
152
- "ui-app-setting-picker-icon hover:bg-accent hover:text-accent-foreground w-[length:var(--control-height)] justify-center border-transparent bg-transparent ps-0 pe-0 shadow-none"
142
+ "ui-app-setting-picker-icon hover:bg-accent hover:text-accent-foreground"
153
143
  ) : (
154
144
  // Labeled: sized to a per-kind width from `sm` up; below `sm` it hugs its content and
155
145
  // caps at the container (`w-auto max-w-full`) instead of the old UNCONDITIONAL
@@ -157,7 +147,7 @@ const AppSettingPicker = React.forwardRef(
157
147
  // fill the bar (gh#165). A form field that wants a full-width control passes
158
148
  // `className="w-full"`, which wins over `w-auto`.
159
149
  // `compact` drops the per-kind width entirely so the trigger hugs its value.
160
- cn("w-auto max-w-full", !isCompact && TRIGGER_WIDTH[kind])
150
+ cn("w-auto max-w-full", !isCompact && "ui-app-setting-picker-trigger")
161
151
  ),
162
152
  // Compact re-tiers the box through tokens (--app-setting-picker-compact-*); the height
163
153
  // still comes from the official --control-height-sm tier, never a literal. The gap is a
@@ -165,13 +155,14 @@ const AppSettingPicker = React.forwardRef(
165
155
  isCompact && "ui-app-setting-picker-compact gap-[length:var(--app-setting-picker-compact-gap)]",
166
156
  className
167
157
  ),
158
+ "data-kind": kind,
168
159
  "aria-label": t(ARIA_KEY[kind]),
169
160
  children: [
170
161
  inline ? null : /* @__PURE__ */ jsx(
171
162
  Icon,
172
163
  {
173
164
  className: cn(
174
- "size-4 shrink-0 opacity-70",
165
+ "ui-app-setting-picker-glyph",
175
166
  // Compact relies on the trigger's tokenized flex gap instead of an icon margin.
176
167
  !iconOnly && !isCompact && "me-2"
177
168
  ),