@godxjp/ui 18.15.3 → 19.0.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 (145) hide show
  1. package/dist/app/date-format-labels.d.ts +1 -1
  2. package/dist/app/time-format-labels.d.ts +1 -1
  3. package/dist/components/data-display/badge.d.ts +1 -1
  4. package/dist/components/data-display/card.d.ts +4 -13
  5. package/dist/components/data-display/card.js +2 -15
  6. package/dist/components/data-display/data-table.d.ts +3 -8
  7. package/dist/components/data-display/data-table.js +106 -68
  8. package/dist/components/data-display/descriptions.js +34 -5
  9. package/dist/components/data-display/empty-state.js +1 -1
  10. package/dist/components/data-display/permission-matrix.js +7 -7
  11. package/dist/components/data-display/popover.js +2 -9
  12. package/dist/components/data-display/qr-code.d.ts +1 -1
  13. package/dist/components/data-display/scroll-area.d.ts +3 -1
  14. package/dist/components/data-display/scroll-area.js +150 -16
  15. package/dist/components/data-display/table.js +12 -3
  16. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  17. package/dist/components/data-entry/calendar.js +15 -24
  18. package/dist/components/data-entry/cascader.js +67 -71
  19. package/dist/components/data-entry/checkbox.js +9 -1
  20. package/dist/components/data-entry/command.d.ts +6 -6
  21. package/dist/components/data-entry/date-picker.js +28 -15
  22. package/dist/components/data-entry/date-range-picker.js +10 -10
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/index.d.ts +2 -1
  25. package/dist/components/data-entry/input.js +8 -19
  26. package/dist/components/data-entry/label.js +4 -2
  27. package/dist/components/data-entry/month-picker.js +16 -15
  28. package/dist/components/data-entry/month-range-picker.js +17 -16
  29. package/dist/components/data-entry/radio.js +6 -1
  30. package/dist/components/data-entry/search-select.js +29 -29
  31. package/dist/components/data-entry/select.js +15 -18
  32. package/dist/components/data-entry/switch.js +7 -1
  33. package/dist/components/data-entry/textarea.d.ts +56 -0
  34. package/dist/components/data-entry/textarea.js +103 -40
  35. package/dist/components/data-entry/time-picker.js +52 -52
  36. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  37. package/dist/components/data-entry/toggle.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +14 -15
  39. package/dist/components/data-entry/tree-select.js +16 -22
  40. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +46 -46
  43. package/dist/components/feedback/alert.js +7 -14
  44. package/dist/components/feedback/dialog.d.ts +23 -32
  45. package/dist/components/feedback/dialog.js +16 -13
  46. package/dist/components/feedback/sheet.d.ts +1 -1
  47. package/dist/components/feedback/sheet.js +14 -14
  48. package/dist/components/feedback/skeleton.js +13 -10
  49. package/dist/components/feedback/sonner.d.ts +1 -1
  50. package/dist/components/feedback/sonner.js +17 -8
  51. package/dist/components/feedback/tooltip.js +1 -1
  52. package/dist/components/general/activity.d.ts +44 -0
  53. package/dist/components/general/activity.js +65 -0
  54. package/dist/components/general/button.d.ts +2 -2
  55. package/dist/components/general/button.js +11 -24
  56. package/dist/components/general/index.d.ts +2 -0
  57. package/dist/components/general/index.js +2 -0
  58. package/dist/components/layout/app-shell.js +5 -3
  59. package/dist/components/layout/auth-divider.d.ts +9 -2
  60. package/dist/components/layout/auth-divider.js +7 -11
  61. package/dist/components/layout/index.d.ts +1 -0
  62. package/dist/components/layout/page-container.d.ts +1 -1
  63. package/dist/components/layout/page-container.js +32 -20
  64. package/dist/components/layout/resizable.d.ts +12 -12
  65. package/dist/components/layout/separator.d.ts +1 -0
  66. package/dist/components/layout/service-role-panel.js +47 -42
  67. package/dist/components/layout/sidebar.js +13 -1
  68. package/dist/components/layout/split-pane.d.ts +12 -1
  69. package/dist/components/layout/split-pane.js +13 -4
  70. package/dist/components/navigation/app-setting-picker.js +5 -14
  71. package/dist/components/navigation/dropdown-menu.js +10 -16
  72. package/dist/components/navigation/menubar.d.ts +3 -6
  73. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  74. package/dist/components/navigation/pagination-utils.js +27 -21
  75. package/dist/components/navigation/pagination.d.ts +4 -4
  76. package/dist/components/navigation/pagination.js +2 -2
  77. package/dist/components/navigation/steps.js +31 -67
  78. package/dist/components/navigation/tabs.js +17 -5
  79. package/dist/components/query/infinite-query-state.js +2 -2
  80. package/dist/components/ui/accordion.d.ts +1 -1
  81. package/dist/components/ui/avatar.d.ts +1 -1
  82. package/dist/components/ui/avatar.js +29 -10
  83. package/dist/components/ui/input-otp.d.ts +8 -6
  84. package/dist/components/ui/rating.js +1 -1
  85. package/dist/components/ui/separator.d.ts +27 -2
  86. package/dist/components/ui/separator.js +42 -11
  87. package/dist/components/ui/toggle-group.d.ts +15 -5
  88. package/dist/components/ui/toggle-group.js +43 -18
  89. package/dist/components/ui/toggle.d.ts +66 -5
  90. package/dist/components/ui/toggle.js +64 -9
  91. package/dist/i18n/messages/en.json +8 -0
  92. package/dist/i18n/messages/ja.json +8 -0
  93. package/dist/i18n/messages/vi.json +8 -0
  94. package/dist/lib/control-styles.d.ts +14 -0
  95. package/dist/lib/control-styles.js +2 -0
  96. package/dist/props/components/data-display.prop.d.ts +85 -3
  97. package/dist/props/components/data-entry.prop.d.ts +17 -0
  98. package/dist/props/components/general.prop.d.ts +52 -1
  99. package/dist/props/components/layout.prop.d.ts +137 -2
  100. package/dist/props/registry.d.ts +116 -5
  101. package/dist/props/registry.js +165 -4
  102. package/dist/props/vocabulary/index.d.ts +2 -2
  103. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  104. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  105. package/dist/styles/alert-layout.css +69 -3
  106. package/dist/styles/badge-layout.css +2 -2
  107. package/dist/styles/base.css +6 -0
  108. package/dist/styles/card-layout.css +27 -7
  109. package/dist/styles/control.css +1150 -29
  110. package/dist/styles/data-display-layout.css +185 -11
  111. package/dist/styles/data-entry-layout.css +352 -1
  112. package/dist/styles/density.css +2 -2
  113. package/dist/styles/dialog-layout.css +203 -4
  114. package/dist/styles/focus-ring.css +155 -0
  115. package/dist/styles/form-layout.css +50 -5
  116. package/dist/styles/index.css +1 -0
  117. package/dist/styles/layout.css +412 -23
  118. package/dist/styles/motion.css +178 -0
  119. package/dist/styles/navigation-layout.css +398 -22
  120. package/dist/styles/shell-layout.css +137 -39
  121. package/dist/styles/table-layout.css +153 -22
  122. package/dist/styles/toggle.css +59 -0
  123. package/dist/tokens/base.css +5 -0
  124. package/dist/tokens/components/activity.css +64 -0
  125. package/dist/tokens/components/badge.css +7 -0
  126. package/dist/tokens/components/card.css +14 -10
  127. package/dist/tokens/components/chart.css +1 -1
  128. package/dist/tokens/components/control.css +358 -8
  129. package/dist/tokens/components/data-display.css +75 -0
  130. package/dist/tokens/components/data-entry.css +25 -0
  131. package/dist/tokens/components/descriptions.css +22 -0
  132. package/dist/tokens/components/feedback.css +143 -4
  133. package/dist/tokens/components/form.css +16 -0
  134. package/dist/tokens/components/legal-document.css +5 -1
  135. package/dist/tokens/components/navigation.css +109 -0
  136. package/dist/tokens/components/scroll-area.css +21 -0
  137. package/dist/tokens/components/separator.css +52 -0
  138. package/dist/tokens/components/shell.css +132 -20
  139. package/dist/tokens/components/table.css +52 -3
  140. package/dist/tokens/components/toggle.css +82 -0
  141. package/dist/tokens/components/upload.css +107 -0
  142. package/dist/tokens/foundation.css +171 -10
  143. package/dist/tokens/semantic/layout.css +120 -1
  144. package/package.json +84 -68
  145. package/scripts/ui-audit.mjs +51 -2
@@ -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
  ),
@@ -29,7 +29,7 @@ const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...pr
29
29
  "data-slot": "dropdown-menu-content",
30
30
  sideOffset,
31
31
  className: cn(
32
- "bg-popover text-popover-foreground data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-32 origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
32
+ "ui-dropdown-menu-content data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 origin-[var(--radix-dropdown-menu-content-transform-origin)]",
33
33
  className
34
34
  ),
35
35
  ...props
@@ -44,7 +44,7 @@ const DropdownMenuItem = React.forwardRef(({ className, inset, variant = "defaul
44
44
  "data-inset": inset,
45
45
  "data-variant": variant,
46
46
  className: cn(
47
- "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset=true]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
47
+ "ui-dropdown-menu-item [&_svg:not([class*='text-'])]:text-muted-foreground",
48
48
  className
49
49
  ),
50
50
  ...props
@@ -57,7 +57,7 @@ const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref)
57
57
  ref,
58
58
  "data-slot": "dropdown-menu-label",
59
59
  "data-inset": inset,
60
- className: cn("px-2 py-1.5 text-sm font-medium data-[inset=true]:ps-8", className),
60
+ className: cn("ui-dropdown-menu-label", className),
61
61
  ...props
62
62
  }
63
63
  ));
@@ -67,7 +67,7 @@ const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) =>
67
67
  {
68
68
  ref,
69
69
  "data-slot": "dropdown-menu-separator",
70
- className: cn("bg-border -mx-1 my-1 h-px", className),
70
+ className: cn("ui-dropdown-menu-separator", className),
71
71
  ...props
72
72
  }
73
73
  ));
@@ -77,14 +77,11 @@ const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, checke
77
77
  {
78
78
  ref,
79
79
  "data-slot": "dropdown-menu-checkbox-item",
80
- className: cn(
81
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 ps-8 pe-2 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
82
- className
83
- ),
80
+ className: cn("ui-dropdown-menu-checkbox-item", className),
84
81
  checked,
85
82
  ...props,
86
83
  children: [
87
- /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute start-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "size-4", "aria-hidden": "true" }) }) }),
84
+ /* @__PURE__ */ jsx("span", { className: "ui-dropdown-menu-indicator-slot", children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "ui-dropdown-menu-check", "aria-hidden": "true" }) }) }),
88
85
  children
89
86
  ]
90
87
  }
@@ -95,10 +92,7 @@ const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props
95
92
  {
96
93
  ref,
97
94
  "data-slot": "dropdown-menu-radio-item",
98
- className: cn(
99
- "focus:bg-accent focus:text-accent-foreground data-[state=checked]:bg-primary/10 data-[state=checked]:text-primary relative flex cursor-default items-center gap-2 rounded-sm px-2.5 py-1.5 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[state=checked]:font-medium [&_svg]:pointer-events-none [&_svg]:shrink-0",
100
- className
101
- ),
95
+ className: cn("ui-dropdown-menu-radio-item", className),
102
96
  ...props,
103
97
  children
104
98
  }
@@ -111,13 +105,13 @@ const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, .
111
105
  "data-slot": "dropdown-menu-sub-trigger",
112
106
  "data-inset": inset,
113
107
  className: cn(
114
- "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors outline-none select-none data-[inset=true]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
108
+ "ui-dropdown-menu-sub-trigger [&_svg:not([class*='text-'])]:text-muted-foreground",
115
109
  className
116
110
  ),
117
111
  ...props,
118
112
  children: [
119
113
  children,
120
- /* @__PURE__ */ jsx(ChevronRight, { className: "ms-auto size-4", "aria-hidden": "true" })
114
+ /* @__PURE__ */ jsx(ChevronRight, { className: "ui-dropdown-menu-sub-trigger-icon", "aria-hidden": "true" })
121
115
  ]
122
116
  }
123
117
  ));
@@ -128,7 +122,7 @@ const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) =
128
122
  ref,
129
123
  "data-slot": "dropdown-menu-sub-content",
130
124
  className: cn(
131
- "bg-popover text-popover-foreground data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 z-50 min-w-32 origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-hidden rounded-md border p-1 shadow-lg",
125
+ "ui-dropdown-menu-sub-content data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 origin-[var(--radix-dropdown-menu-content-transform-origin)]",
132
126
  className
133
127
  ),
134
128
  ...props
@@ -1,12 +1,9 @@
1
1
  import * as React from "react";
2
2
  import * as MenubarPrimitive from "@radix-ui/react-menubar";
3
3
  export declare const Menubar: React.ForwardRefExoticComponent<MenubarPrimitive.MenubarProps & React.RefAttributes<HTMLDivElement>>;
4
- export declare const MenubarMenu: {
5
- (props: MenubarPrimitive.MenubarMenuProps & {
6
- __scopeMenubar?: import("@radix-ui/react-context").Scope;
7
- }): React.JSX.Element;
8
- displayName: string;
9
- };
4
+ export declare const MenubarMenu: (props: MenubarPrimitive.MenubarMenuProps & {
5
+ __scopeMenubar?: import("@radix-ui/react-context").Scope;
6
+ }) => React.JSX.Element;
10
7
  export declare function MenubarTrigger(props: React.ComponentProps<typeof MenubarPrimitive.Trigger>): React.JSX.Element;
11
8
  export declare const MenubarContent: React.ForwardRefExoticComponent<Omit<MenubarPrimitive.MenubarContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
12
9
  export declare const MenubarItem: React.ForwardRefExoticComponent<Omit<MenubarPrimitive.MenubarItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
@@ -1,2 +1,15 @@
1
- /** Build visible page numbers with ellipsis — Ant Design pagination style. */
2
- export declare function buildPageRange(current: number, totalPages: number, siblingCount?: number): (number | "ellipsis")[];
1
+ /**
2
+ * Build visible page numbers with ellipsis Ant Design / MUI pagination style.
3
+ *
4
+ * The item count is CONSTANT wherever the current page sits. The earlier version
5
+ * derived the window straight from `current ± siblingCount` and let it collapse
6
+ * against the edges, so a 1,060-page list opened as `1 2 … 1060` — four controls
7
+ * with a wide empty gap, and both a bad target size and a bad sense of scale
8
+ * (reported 2026-08-24). Ant Design and MUI both solve this by CLAMPING the
9
+ * window instead of shrinking it: near an edge the window slides inward so the
10
+ * strip keeps its full width. Same list now opens as `1 2 3 4 5 … 1060`.
11
+ *
12
+ * Slots = `boundaryCount * 2` edges + `siblingCount * 2 + 1` around current + 2
13
+ * ellipsis positions. With the defaults that is 7 controls.
14
+ */
15
+ export declare function buildPageRange(current: number, totalPages: number, siblingCount?: number, boundaryCount?: number): (number | "ellipsis")[];
@@ -1,24 +1,30 @@
1
- function buildPageRange(current, totalPages, siblingCount = 1) {
2
- if (totalPages <= 1) return totalPages === 1 ? [1] : [];
3
- const totalNumbers = siblingCount * 2 + 5;
4
- if (totalPages <= totalNumbers) {
5
- return Array.from({ length: totalPages }, (_, i) => i + 1);
6
- }
7
- const left = Math.max(current - siblingCount, 1);
8
- const right = Math.min(current + siblingCount, totalPages);
9
- const showLeftEllipsis = left > 2;
10
- const showRightEllipsis = right < totalPages - 1;
11
- const range = [];
12
- range.push(1);
13
- if (showLeftEllipsis) range.push("ellipsis");
14
- else for (let i = 2; i < left; i++) range.push(i);
15
- for (let i = left; i <= right; i++) {
16
- if (i !== 1 && i !== totalPages) range.push(i);
17
- }
18
- if (showRightEllipsis) range.push("ellipsis");
19
- else for (let i = right + 1; i < totalPages; i++) range.push(i);
20
- if (totalPages > 1) range.push(totalPages);
21
- return range;
1
+ function range(start, end) {
2
+ return end < start ? [] : Array.from({ length: end - start + 1 }, (_, i) => start + i);
3
+ }
4
+ function buildPageRange(current, totalPages, siblingCount = 1, boundaryCount = 1) {
5
+ if (totalPages <= 0) return [];
6
+ if (totalPages === 1) return [1];
7
+ const totalNumbers = boundaryCount * 2 + siblingCount * 2 + 3;
8
+ if (totalPages <= totalNumbers) return range(1, totalPages);
9
+ const startPages = range(1, Math.min(boundaryCount, totalPages));
10
+ const endPages = range(Math.max(totalPages - boundaryCount + 1, boundaryCount + 1), totalPages);
11
+ const siblingsStart = Math.max(
12
+ Math.min(current - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1),
13
+ boundaryCount + 2
14
+ );
15
+ const siblingsEnd = Math.min(
16
+ Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),
17
+ endPages.length > 0 ? endPages[0] - 2 : totalPages - 1
18
+ );
19
+ return [
20
+ ...startPages,
21
+ // An ellipsis that would hide exactly ONE page is pointless — spell that page out
22
+ // instead (Ant/MUI do the same), which is also what keeps the count constant.
23
+ ...siblingsStart > boundaryCount + 2 ? ["ellipsis"] : boundaryCount + 1 < totalPages - boundaryCount ? [boundaryCount + 1] : [],
24
+ ...range(siblingsStart, siblingsEnd),
25
+ ...siblingsEnd < totalPages - boundaryCount - 1 ? ["ellipsis"] : totalPages - boundaryCount > boundaryCount ? [totalPages - boundaryCount] : [],
26
+ ...endPages
27
+ ];
22
28
  }
23
29
  export {
24
30
  buildPageRange
@@ -8,10 +8,10 @@ declare const PaginationLink: React.ForwardRefExoticComponent<Omit<Omit<React.De
8
8
  disabled?: boolean;
9
9
  href?: string;
10
10
  } & React.RefAttributes<HTMLButtonElement>>;
11
- declare const PaginationEllipsis: {
12
- ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>): React.JSX.Element;
13
- displayName: string;
14
- };
11
+ declare function PaginationEllipsis({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>): React.JSX.Element;
12
+ declare namespace PaginationEllipsis {
13
+ var displayName: string;
14
+ }
15
15
  declare const PaginationPrevious: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "type"> & {
16
16
  isActive?: boolean;
17
17
  disabled?: boolean;
@@ -142,7 +142,7 @@ function Pagination({
142
142
  disabled: Boolean(disabled) || safeCurrent <= 1,
143
143
  "aria-label": t("navigation.pagination.prev"),
144
144
  onClick: () => go(safeCurrent - 1),
145
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
145
+ children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true" })
146
146
  }
147
147
  ),
148
148
  /* @__PURE__ */ jsxs("span", { className: "ui-pagination-count", children: [
@@ -159,7 +159,7 @@ function Pagination({
159
159
  disabled: Boolean(disabled) || safeCurrent >= totalPages,
160
160
  "aria-label": t("navigation.pagination.next"),
161
161
  onClick: () => go(safeCurrent + 1),
162
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
162
+ children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
163
163
  }
164
164
  )
165
165
  ] });