@parallelworks/ui 0.51.0 → 0.53.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 (250) hide show
  1. package/README.md +197 -17
  2. package/dist/ai/agent/toolStrings.d.ts +2 -0
  3. package/dist/ai/agent/toolStrings.js +20 -18
  4. package/dist/ai/ai.css +17 -3
  5. package/dist/ai/components/AllocationSelector.js +5 -1
  6. package/dist/ai/components/AttachmentManager.js +51 -42
  7. package/dist/ai/components/AttachmentUpload.d.ts +4 -2
  8. package/dist/ai/components/AttachmentUpload.js +9 -9
  9. package/dist/ai/components/BlockedGroupBanner.js +8 -14
  10. package/dist/ai/components/BranchNavigator.js +11 -12
  11. package/dist/ai/components/ChatEmptyState.js +12 -19
  12. package/dist/ai/components/ChatInput.d.ts +4 -3
  13. package/dist/ai/components/ChatInput.js +27 -13
  14. package/dist/ai/components/ChatMessage.js +45 -121
  15. package/dist/ai/components/ChatMessageList.d.ts +5 -2
  16. package/dist/ai/components/ChatMessageList.js +89 -51
  17. package/dist/ai/components/ChatSidebar.js +11 -7
  18. package/dist/ai/components/ChatThread.js +1 -13
  19. package/dist/ai/components/DragOverlay.js +3 -3
  20. package/dist/ai/components/MessageAttachments.d.ts +10 -0
  21. package/dist/ai/components/MessageAttachments.js +78 -0
  22. package/dist/ai/components/ModelSelector.js +3 -3
  23. package/dist/ai/components/PastedText.js +2 -2
  24. package/dist/ai/components/ProviderIssueBanner.js +19 -23
  25. package/dist/ai/components/ShareDialog.js +2 -2
  26. package/dist/ai/components/agent/ActivityLine.d.ts +0 -2
  27. package/dist/ai/components/agent/ActivityLine.js +8 -51
  28. package/dist/ai/components/agent/AgentMessageParts.js +114 -53
  29. package/dist/ai/core/ChatProvider.d.ts +16 -5
  30. package/dist/ai/core/ChatProvider.js +41 -8
  31. package/dist/ai/core/config.js +4 -3
  32. package/dist/ai/index.d.ts +1 -1
  33. package/dist/ai/index.js +3 -3
  34. package/dist/ai/strings.d.ts +29 -3
  35. package/dist/ai/strings.js +80 -26
  36. package/dist/ai/types.d.ts +4 -0
  37. package/dist/ai/ui/Notice.d.ts +2 -2
  38. package/dist/ai/ui/Notice.js +29 -62
  39. package/dist/ai/ui/sidebar.d.ts +5 -3
  40. package/dist/ai/ui/sidebar.js +22 -10
  41. package/dist/components/ActionMenu.d.ts +1 -0
  42. package/dist/components/ActionMenu.js +6 -1
  43. package/dist/components/AppLink.d.ts +26 -0
  44. package/dist/components/AppLink.js +51 -0
  45. package/dist/components/Avatar.d.ts +2 -1
  46. package/dist/components/Avatar.js +4 -2
  47. package/dist/components/Button.d.ts +15 -5
  48. package/dist/components/Button.js +47 -40
  49. package/dist/components/Callout.d.ts +31 -7
  50. package/dist/components/Callout.js +50 -19
  51. package/dist/components/Card.d.ts +11 -6
  52. package/dist/components/Card.js +14 -8
  53. package/dist/components/Checkbox.js +2 -1
  54. package/dist/components/ColorInput.js +5 -3
  55. package/dist/components/Combobox.js +49 -19
  56. package/dist/components/ConfirmModal.d.ts +19 -8
  57. package/dist/components/ConfirmModal.js +31 -53
  58. package/dist/components/CopyButton.d.ts +38 -0
  59. package/dist/components/CopyButton.js +68 -0
  60. package/dist/components/CreateModal.js +118 -111
  61. package/dist/components/DatePicker.js +12 -8
  62. package/dist/components/DescriptionList.d.ts +25 -5
  63. package/dist/components/DescriptionList.js +56 -22
  64. package/dist/components/Disclosure.js +1 -1
  65. package/dist/components/Drawer.js +10 -16
  66. package/dist/components/Field.d.ts +16 -5
  67. package/dist/components/Field.js +63 -8
  68. package/dist/components/IconButton.d.ts +14 -5
  69. package/dist/components/IconButton.js +25 -14
  70. package/dist/components/InfoTip.d.ts +12 -1
  71. package/dist/components/InfoTip.js +13 -2
  72. package/dist/components/Input.d.ts +14 -8
  73. package/dist/components/Input.js +46 -31
  74. package/dist/components/Loader.js +1 -1
  75. package/dist/components/MarkdownImpl.d.ts +1 -0
  76. package/dist/components/MarkdownImpl.js +1 -0
  77. package/dist/components/MenuEntries.d.ts +4 -0
  78. package/dist/components/MenuEntries.js +11 -12
  79. package/dist/components/Meter.js +5 -4
  80. package/dist/components/Modal.d.ts +28 -4
  81. package/dist/components/Modal.js +37 -14
  82. package/dist/components/Overview.d.ts +20 -12
  83. package/dist/components/Overview.js +14 -9
  84. package/dist/components/PageHeader.d.ts +47 -65
  85. package/dist/components/PageHeader.js +275 -153
  86. package/dist/components/PageHeaderAction.d.ts +67 -0
  87. package/dist/components/PageHeaderAction.js +107 -0
  88. package/dist/components/Pagination.js +6 -16
  89. package/dist/components/Popover.d.ts +4 -1
  90. package/dist/components/Popover.js +5 -3
  91. package/dist/components/Provider.d.ts +79 -19
  92. package/dist/components/Provider.js +140 -130
  93. package/dist/components/Radio.js +2 -1
  94. package/dist/components/RefreshButton.js +7 -8
  95. package/dist/components/RequiredMark.js +1 -1
  96. package/dist/components/Section.js +1 -1
  97. package/dist/components/Select.d.ts +4 -9
  98. package/dist/components/Select.js +24 -11
  99. package/dist/components/SettingModeOption.d.ts +1 -1
  100. package/dist/components/SettingModeOption.js +7 -1
  101. package/dist/components/SettingsCard.d.ts +5 -3
  102. package/dist/components/SettingsCard.js +27 -20
  103. package/dist/components/Slider.js +1 -1
  104. package/dist/components/StatusBadge.js +2 -2
  105. package/dist/components/Switch.js +5 -4
  106. package/dist/components/Table.d.ts +30 -20
  107. package/dist/components/Table.js +50 -73
  108. package/dist/components/Tabs.d.ts +3 -7
  109. package/dist/components/Tabs.js +6 -10
  110. package/dist/components/ThemeEditor.js +18 -8
  111. package/dist/components/Toast.d.ts +11 -3
  112. package/dist/components/Toast.js +45 -20
  113. package/dist/components/buttonClasses.js +1 -1
  114. package/dist/components/comboboxItems.d.ts +4 -0
  115. package/dist/components/comboboxItems.js +6 -1
  116. package/dist/components/dismiss.d.ts +12 -0
  117. package/dist/components/dismiss.js +19 -0
  118. package/dist/components/fieldBox.d.ts +4 -0
  119. package/dist/components/fieldBox.js +7 -3
  120. package/dist/components/floating.d.ts +3 -3
  121. package/dist/components/floating.js +1 -1
  122. package/dist/components/focus.d.ts +5 -0
  123. package/dist/components/focus.js +21 -1
  124. package/dist/components/markdownCodeBreaks.js +3 -4
  125. package/dist/components/pageHeaderStore.d.ts +41 -0
  126. package/dist/components/pageHeaderStore.js +67 -0
  127. package/dist/components/pathBreaks.d.ts +8 -0
  128. package/dist/components/pathBreaks.js +28 -0
  129. package/dist/components/segmented.js +1 -1
  130. package/dist/components/shortcutKey.d.ts +2 -1
  131. package/dist/components/shortcutKey.js +19 -2
  132. package/dist/components/splitter.js +1 -1
  133. package/dist/components/useCssIsDark.js +40 -18
  134. package/dist/components/useTheme.js +9 -10
  135. package/dist/dev.d.ts +9 -0
  136. package/dist/dev.js +14 -0
  137. package/dist/editor/Editor.d.ts +3 -2
  138. package/dist/editor/Editor.js +24 -5
  139. package/dist/editor/EditorField.d.ts +2 -1
  140. package/dist/editor/EditorField.js +11 -29
  141. package/dist/editor/FormCodePanel.d.ts +7 -3
  142. package/dist/editor/FormCodePanel.js +14 -7
  143. package/dist/editor/Monaco.d.ts +22 -6
  144. package/dist/editor/Monaco.js +177 -113
  145. package/dist/editor/editorModel.d.ts +1 -1
  146. package/dist/editor/editorModel.js +1 -1
  147. package/dist/editor/index.d.ts +6 -5
  148. package/dist/editor/index.js +5 -4
  149. package/dist/editor/languages.d.ts +24 -0
  150. package/dist/editor/languages.js +31 -0
  151. package/dist/editor/monacoFeatures.d.ts +7 -0
  152. package/dist/editor/monacoFeatures.js +19 -0
  153. package/dist/editor/themes.d.ts +8 -5
  154. package/dist/editor/themes.js +65 -32
  155. package/dist/editor/workers.d.ts +3 -3
  156. package/dist/editor/workers.js +8 -4
  157. package/dist/editor/yaml.d.ts +6 -2
  158. package/dist/editor/yaml.js +11 -2
  159. package/dist/editor/yamlLanguage.js +55 -38
  160. package/dist/file-explorer/FileExplorer.d.ts +18 -4
  161. package/dist/file-explorer/FileExplorer.js +204 -142
  162. package/dist/file-explorer/FileExplorerContext.js +1 -1
  163. package/dist/file-explorer/FilePreview/StreamedBadge.js +3 -1
  164. package/dist/file-explorer/FilePreview/Viewer.js +14 -10
  165. package/dist/file-explorer/FilePreview/index.js +4 -4
  166. package/dist/file-explorer/FileRow.d.ts +6 -2
  167. package/dist/file-explorer/FileRow.js +35 -17
  168. package/dist/file-explorer/FileRowsWindow.d.ts +2 -1
  169. package/dist/file-explorer/FileRowsWindow.js +36 -4
  170. package/dist/file-explorer/FileTable.d.ts +3 -1
  171. package/dist/file-explorer/FileTable.js +6 -3
  172. package/dist/file-explorer/TreeView.d.ts +5 -1
  173. package/dist/file-explorer/TreeView.js +86 -39
  174. package/dist/file-explorer/helpers.d.ts +2 -1
  175. package/dist/file-explorer/helpers.js +24 -23
  176. package/dist/file-explorer/lib/utils.d.ts +3 -0
  177. package/dist/file-explorer/lib/utils.js +6 -1
  178. package/dist/file-explorer/toasts.js +27 -19
  179. package/dist/file-explorer/treeRows.d.ts +6 -0
  180. package/dist/file-explorer/treeRows.js +19 -1
  181. package/dist/file-explorer/useNearEnd.js +3 -6
  182. package/dist/form/Fields/CheckboxGroupField.js +12 -5
  183. package/dist/form/Fields/DurationField.js +1 -1
  184. package/dist/form/Fields/LabelField.js +1 -1
  185. package/dist/form/Fields/ObjectField.d.ts +1 -1
  186. package/dist/form/Fields/ObjectField.js +3 -2
  187. package/dist/form/Fields/StringField.js +16 -29
  188. package/dist/form/Fields/TextAreaField.js +3 -4
  189. package/dist/form/Form.js +3 -8
  190. package/dist/form/Wizard/WizardNavigation.js +0 -3
  191. package/dist/form/Wizard/utils.js +2 -1
  192. package/dist/form/fieldFrame.d.ts +1 -1
  193. package/dist/form/fieldFrame.js +2 -2
  194. package/dist/form/types/fieldComponentTypes.d.ts +2 -1
  195. package/dist/graph/AnnotationBanner.d.ts +1 -2
  196. package/dist/graph/AnnotationBanner.js +30 -32
  197. package/dist/graph/Connectors.js +4 -1
  198. package/dist/graph/DependencyGraph.js +2 -2
  199. package/dist/graph/GraphEditor.js +11 -9
  200. package/dist/graph/GraphEditorDialogs.js +1 -1
  201. package/dist/graph/GraphNode.js +7 -4
  202. package/dist/graph/InputDialog.js +1 -1
  203. package/dist/graph/InputsEditor.js +14 -11
  204. package/dist/graph/MatrixGroup.js +9 -3
  205. package/dist/graph/Reveal.js +18 -10
  206. package/dist/graph/ShortcutsButton.d.ts +11 -2
  207. package/dist/graph/ShortcutsButton.js +14 -3
  208. package/dist/graph/TreeView.js +35 -18
  209. package/dist/graph/WorkflowSettingsDialog.js +1 -1
  210. package/dist/graph/editorFields.js +1 -1
  211. package/dist/graph/editorRows.js +16 -10
  212. package/dist/graph/editorStrings.d.ts +7 -0
  213. package/dist/graph/editorStrings.js +8 -1
  214. package/dist/graph/inputRefs.js +1 -1
  215. package/dist/graph/settingsViews.js +11 -18
  216. package/dist/graph/stepWith.js +1 -1
  217. package/dist/icons.d.ts +2 -0
  218. package/dist/icons.js +4 -2
  219. package/dist/index.d.ts +12 -12
  220. package/dist/index.js +16 -16
  221. package/dist/list/ActionToolbar.d.ts +42 -6
  222. package/dist/list/ActionToolbar.js +298 -101
  223. package/dist/list/ListPage.d.ts +15 -23
  224. package/dist/list/ListPage.js +42 -43
  225. package/dist/list/ListTable.d.ts +37 -7
  226. package/dist/list/ListTable.js +52 -12
  227. package/dist/list/ListViewControls.js +1 -1
  228. package/dist/list/UserHoverCard.js +7 -15
  229. package/dist/list/index.d.ts +2 -2
  230. package/dist/list/index.js +2 -2
  231. package/dist/list/listSearch.js +12 -10
  232. package/dist/list/listView.d.ts +10 -1
  233. package/dist/list/listView.js +15 -1
  234. package/dist/logviewer/LogViewer.d.ts +4 -2
  235. package/dist/logviewer/LogViewer.js +87 -69
  236. package/dist/logviewer/logviewer.css +3 -2
  237. package/dist/reducedMotion.d.ts +5 -0
  238. package/dist/reducedMotion.js +20 -0
  239. package/dist/safeUrl.d.ts +1 -1
  240. package/dist/safeUrl.js +4 -2
  241. package/dist/styles.css +1 -1
  242. package/package.json +5 -2
  243. package/dist/components/Breadcrumbs.d.ts +0 -37
  244. package/dist/components/Breadcrumbs.js +0 -90
  245. package/dist/components/CopyToClipboard.d.ts +0 -17
  246. package/dist/components/CopyToClipboard.js +0 -66
  247. package/dist/components/Sort.d.ts +0 -9
  248. package/dist/components/Sort.js +0 -42
  249. package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +0 -6
  250. package/dist/file-explorer/FilePreview/CodePreviewEditor.js +0 -64
@@ -3,6 +3,9 @@ import type { RowAction } from './rowMenu.js';
3
3
  export type ToolbarAction = RowAction & {
4
4
  /** A hint on the enabled button. */
5
5
  tooltip?: string | undefined;
6
+ /** The one action the view leads to, filled in the accent. Give it to one
7
+ * action at most: the rest are outlined, so it stands out. */
8
+ primary?: boolean | undefined;
6
9
  hidden?: boolean;
7
10
  };
8
11
  /** A pre-rendered element (e.g. a button component that owns its modal). */
@@ -12,17 +15,50 @@ interface ToolbarElement {
12
15
  hidden?: boolean;
13
16
  }
14
17
  export type ToolbarItem = ToolbarAction | ToolbarElement;
15
- /** A toolbar action as a button: a link with `href`, a toggle with `checked`. */
18
+ /** A toolbar action as a button: a link with `href`, a toggle with `checked`.
19
+ * In an ActionToolbar it is one of its items, reached with the arrow keys. */
16
20
  export declare function ToolbarButton({ action }: {
17
21
  action: ToolbarAction;
18
22
  }): import("react").JSX.Element;
19
- /** Pins a detail-page toolbar just below the sticky breadcrumb strip (top-10 matches the strip's 40px height). */
20
- export declare const stickyToolbarClasses = "sticky top-10 z-(--layer-sticky) bg-(--theme-app-bg)";
21
- /** Action groups left, secondary actions right (collapsing into a `…` menu). */
22
- export declare function ActionToolbar({ groups, right, sticky, }: {
23
+ /** How the toolbar fits its row: the items whose labels fold to their icons,
24
+ * and those moved into the `…` menu, by key. */
25
+ export interface ToolbarFit {
26
+ folded: readonly string[];
27
+ overflow: readonly string[];
28
+ }
29
+ /** An item's widths with its label and folded to its icon. */
30
+ export interface ItemWidth {
31
+ key: string;
32
+ full: number;
33
+ folded: number;
34
+ primary: boolean;
35
+ }
36
+ /**
37
+ * Fits a toolbar to `available` pixels one item at a time, stopping as soon as
38
+ * it fits, so it keeps every label the width allows. Items go lowest priority
39
+ * first: the right-hand ones, then the last group's, and so on, each from the
40
+ * end. First their labels fold to their icons, one by one; then, all folded,
41
+ * they move into the `…` menu in the same order; the primary action never
42
+ * moves, and its label folds only once nothing else is left to try.
43
+ */
44
+ export declare function fitToolbar(groups: ItemWidth[][], right: ItemWidth[], available: number): ToolbarFit;
45
+ /** Pins a toolbar to the top of the page's scroll area while the content scrolls under it. */
46
+ export declare const stickyToolbarClasses = "sticky top-0 z-(--layer-sticky) bg-(--theme-app-bg)";
47
+ /** A view's controls in one WAI-ARIA toolbar: a single tab stop, with the arrow
48
+ * keys moving between its items, the `…` menu and disabled ones included, so
49
+ * their reasons stay reachable. Action groups sit on the left and `right` holds
50
+ * secondary actions at the end. It stays one row: when it runs out of room
51
+ * labels fold to their icons one at a time, then actions move into a `…` menu,
52
+ * the right-hand ones first and the primary action never. Give each action its
53
+ * own icon, since a folded one is known by it, and mark the one action the
54
+ * view leads to `primary`. */
55
+ export declare function ActionToolbar({ groups, right, sticky, label, }: {
23
56
  groups: ToolbarItem[][];
24
57
  right?: ToolbarItem[];
25
- /** Pin below the breadcrumb strip while the page scrolls. Requires the toolbar's parent to span the page content. */
58
+ /** Pins it to the top of the page's scroll area while the page scrolls. */
26
59
  sticky?: boolean;
60
+ /** Names the toolbar for assistive technology, such as "Log controls";
61
+ * defaults to "Actions". */
62
+ label?: string | undefined;
27
63
  }): import("react").JSX.Element;
28
64
  export {};
@@ -1,57 +1,94 @@
1
1
  import { cx } from "../cx.js";
2
- import { MoreIcon } from "../icons.js";
3
- import { MenuPopup } from "../components/Menu.js";
4
2
  import { buttonClasses } from "../components/buttonClasses.js";
3
+ import { MoreIcon } from "../icons.js";
4
+ import { MenuPopup, MenuSeparator } from "../components/Menu.js";
5
+ import { isDev } from "../dev.js";
5
6
  import { Tooltip } from "../components/Tooltip.js";
6
- import { useLink, useStrings } from "../components/Provider.js";
7
+ import { useStrings } from "../components/Provider.js";
8
+ import { AppLink } from "../components/AppLink.js";
7
9
  import { MenuEntries, useRunAction, warnIfInert } from "../components/MenuEntries.js";
8
- import { Button } from "../components/Button.js";
9
10
  import { Menu } from "@base-ui/react/menu";
10
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
- import { Fragment as Fragment$1, createContext, useContext, useLayoutEffect, useRef, useState } from "react";
12
+ import { Fragment as Fragment$1, cloneElement, createContext, isValidElement, use, useLayoutEffect, useRef, useState } from "react";
13
+ import { Button } from "@base-ui/react/button";
12
14
  import { Toggle } from "@base-ui/react/toggle";
15
+ import { Toolbar } from "@base-ui/react/toolbar";
13
16
  //#region src/list/ActionToolbar.tsx
14
- var OverflowMenuContext = createContext(false);
15
- function ToolbarItemView({ item }) {
16
- if ("element" in item) return /* @__PURE__ */ jsx(Fragment, { children: item.element });
17
- return /* @__PURE__ */ jsx(ToolbarButton, { action: item });
17
+ var PlacementContext = createContext("alone");
18
+ var FoldedContext = createContext(false);
19
+ /** A toolbar button's classes: outlined unless it's the view's one primary
20
+ * action, so the toolbar has a hierarchy; a toggle shows its state by
21
+ * `aria-pressed` instead of a fill. A destructive one reads in the error
22
+ * color, as it does in a menu. */
23
+ function actionClasses(action, iconOnly) {
24
+ const primary = action.primary && action.checked === void 0;
25
+ return cx(buttonClasses({
26
+ variant: primary ? "primary" : "outline",
27
+ size: "sm",
28
+ iconOnly
29
+ }), action.destructive && !primary && "text-(--theme-error-text)");
18
30
  }
19
- /** A toolbar action as a button: a link with `href`, a toggle with `checked`. */
31
+ /** A toolbar action as a button: a link with `href`, a toggle with `checked`.
32
+ * In an ActionToolbar it is one of its items, reached with the arrow keys. */
20
33
  function ToolbarButton({ action }) {
21
- const inOverflowMenu = useContext(OverflowMenuContext);
22
- const Link = useLink();
34
+ const placement = use(PlacementContext);
23
35
  const runAction = useRunAction();
24
- if (inOverflowMenu) return /* @__PURE__ */ jsx(MenuEntries, { entries: [action] });
25
- const className = buttonClasses({
26
- variant: action.checked === false ? "outline" : "primary",
27
- size: "md"
28
- });
29
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [action.icon, action.label] });
36
+ const folded = use(FoldedContext);
37
+ if (placement === "menu") return /* @__PURE__ */ jsx(MenuEntries, { entries: [action] });
30
38
  warnIfInert(action, "ActionToolbar");
39
+ const iconOnly = Boolean(action.icon) && placement === "toolbar" && folded;
40
+ const className = actionClasses(action, iconOnly);
41
+ const reason = action.disabled ? action.disabledReason : void 0;
42
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
43
+ action.icon,
44
+ iconOnly ? /* @__PURE__ */ jsx("span", {
45
+ className: "sr-only",
46
+ children: action.label
47
+ }) : action.label,
48
+ reason && /* @__PURE__ */ jsxs("span", {
49
+ className: "sr-only",
50
+ children: [". ", reason]
51
+ })
52
+ ] });
31
53
  const run = () => runAction(action);
54
+ const inToolbar = placement === "toolbar";
32
55
  let control;
33
- if (action.href && !action.disabled) control = /* @__PURE__ */ jsx(Link, {
34
- href: action.href,
35
- className,
36
- children: content
37
- });
38
- else if (action.checked !== void 0) control = /* @__PURE__ */ jsx(Toggle, {
39
- pressed: action.checked,
40
- onPressedChange: run,
41
- disabled: action.disabled,
42
- className,
43
- children: content
44
- });
45
- else control = /* @__PURE__ */ jsx(Button, {
46
- variant: "primary",
47
- size: "md",
48
- onClick: run,
49
- disabled: action.disabled,
50
- focusableWhenDisabled: true,
51
- children: content
52
- });
56
+ if (action.href) {
57
+ const link = /* @__PURE__ */ jsx(AppLink, {
58
+ href: action.href,
59
+ reloadDocument: action.reloadDocument,
60
+ target: action.target,
61
+ disabled: action.disabled,
62
+ className
63
+ });
64
+ control = inToolbar ? /* @__PURE__ */ jsx(Toolbar.Link, {
65
+ render: link,
66
+ children: content
67
+ }) : cloneElement(link, void 0, content);
68
+ } else if (action.checked !== void 0) {
69
+ const toggle = /* @__PURE__ */ jsx(Toggle, {
70
+ pressed: action.checked,
71
+ onPressedChange: run,
72
+ disabled: action.disabled,
73
+ className
74
+ });
75
+ control = inToolbar ? /* @__PURE__ */ jsx(Toolbar.Button, {
76
+ render: toggle,
77
+ disabled: action.disabled,
78
+ children: content
79
+ }) : cloneElement(toggle, void 0, content);
80
+ } else {
81
+ const Control = inToolbar ? Toolbar.Button : Button;
82
+ control = /* @__PURE__ */ jsx(Control, {
83
+ onClick: run,
84
+ disabled: action.disabled,
85
+ focusableWhenDisabled: true,
86
+ className,
87
+ children: content
88
+ });
89
+ }
53
90
  return /* @__PURE__ */ jsx(Tooltip, {
54
- content: action.disabled ? action.disabledReason : action.tooltip,
91
+ content: reason ?? (iconOnly ? action.tooltip ?? action.label : action.tooltip),
55
92
  side: "bottom",
56
93
  children: control
57
94
  });
@@ -60,11 +97,12 @@ function OverflowMenu({ items }) {
60
97
  const { list: t } = useStrings();
61
98
  return /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsx(Tooltip, {
62
99
  content: t.moreActions,
63
- children: /* @__PURE__ */ jsx(Menu.Trigger, {
100
+ children: /* @__PURE__ */ jsx(Toolbar.Button, {
101
+ render: /* @__PURE__ */ jsx(Menu.Trigger, {}),
64
102
  "aria-label": t.moreActions,
65
103
  className: buttonClasses({
66
104
  variant: "outline",
67
- size: "md",
105
+ size: "sm",
68
106
  iconOnly: true
69
107
  }),
70
108
  "data-testid": "action-toolbar-menu-button",
@@ -74,80 +112,239 @@ function OverflowMenu({ items }) {
74
112
  keepMounted: true,
75
113
  align: "end",
76
114
  className: "min-w-56",
77
- children: /* @__PURE__ */ jsx(OverflowMenuContext.Provider, {
78
- value: true,
79
- children: items.map((item) => "element" in item ? /* @__PURE__ */ jsx(Fragment$1, { children: item.element }, item.key) : /* @__PURE__ */ jsx(MenuEntries, { entries: [item] }, item.key))
115
+ children: /* @__PURE__ */ jsx(PlacementContext, {
116
+ value: "menu",
117
+ children: items.map((group, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [i > 0 && /* @__PURE__ */ jsx(MenuSeparator, {}), group.map((item) => "element" in item ? /* @__PURE__ */ jsx(Fragment$1, { children: item.element }, item.key) : /* @__PURE__ */ jsx(MenuEntries, { entries: [item] }, item.key))] }, group[0]?.key ?? i))
80
118
  })
81
119
  })] });
82
120
  }
83
- var MENU_BUTTON_WIDTH = 40;
121
+ var FITS_ALL = {
122
+ folded: [],
123
+ overflow: []
124
+ };
125
+ var GROUP_GAP = 10;
84
126
  var ITEM_GAP = 4;
85
- function fitCount(widths, available, gap, menuWidth) {
86
- if (widths.length === 0) return 0;
87
- if (widths.reduce((sum, w) => sum + w, 0) + gap * (widths.length - 1) <= available) return widths.length;
88
- let used = menuWidth;
89
- let count = 0;
90
- for (const w of widths) {
91
- if (used + gap + w > available) break;
92
- used += gap + w;
93
- count++;
127
+ var MENU_BUTTON_WIDTH = 28;
128
+ function rowWidth(groups, right, fit) {
129
+ const hidden = new Set(fit.overflow);
130
+ const folded = new Set(fit.folded);
131
+ const width = (item) => folded.has(item.key) ? item.folded : item.full;
132
+ const run = (items) => items.reduce((sum, item) => sum + width(item), 0) + ITEM_GAP * (items.length - 1);
133
+ const shownGroups = groups.map((group) => group.filter((item) => !hidden.has(item.key))).filter((group) => group.length > 0);
134
+ let total = shownGroups.reduce((sum, group) => sum + run(group), 0);
135
+ total += (shownGroups.length - 1) * 21;
136
+ const shownRight = right.filter((item) => !hidden.has(item.key));
137
+ const end = (shownRight.length > 0 ? run(shownRight) : 0) + (hidden.size > 0 ? MENU_BUTTON_WIDTH + (shownRight.length > 0 ? ITEM_GAP : 0) : 0);
138
+ if (end > 0) total += end + (shownGroups.length > 0 ? GROUP_GAP : 0);
139
+ return Math.max(total, 0);
140
+ }
141
+ /**
142
+ * Fits a toolbar to `available` pixels one item at a time, stopping as soon as
143
+ * it fits, so it keeps every label the width allows. Items go lowest priority
144
+ * first: the right-hand ones, then the last group's, and so on, each from the
145
+ * end. First their labels fold to their icons, one by one; then, all folded,
146
+ * they move into the `…` menu in the same order; the primary action never
147
+ * moves, and its label folds only once nothing else is left to try.
148
+ */
149
+ function fitToolbar(groups, right, available) {
150
+ const fits = (fit) => rowWidth(groups, right, fit) <= available + .5;
151
+ if (fits(FITS_ALL)) return FITS_ALL;
152
+ const byPriority = [...right].reverse().concat(...[...groups].reverse().map((g) => [...g].reverse()));
153
+ const rest = byPriority.filter((item) => !item.primary);
154
+ const folded = [];
155
+ for (const item of rest) {
156
+ if (item.folded >= item.full) continue;
157
+ folded.push(item.key);
158
+ const fit = {
159
+ folded: [...folded],
160
+ overflow: []
161
+ };
162
+ if (fits(fit)) return fit;
94
163
  }
95
- return count;
164
+ const overflow = [];
165
+ for (const item of rest) {
166
+ overflow.push(item.key);
167
+ const fit = {
168
+ folded: [...folded],
169
+ overflow: [...overflow]
170
+ };
171
+ if (fits(fit)) return fit;
172
+ }
173
+ const primary = byPriority.filter((item) => item.primary).map((item) => item.key);
174
+ return {
175
+ folded: [...folded, ...primary],
176
+ overflow
177
+ };
96
178
  }
97
- /** Measures in a layout effect (before paint) so the inline/overflow split never flashes. */
98
- function CollapsibleRight({ items }) {
99
- const wrapRef = useRef(null);
100
- const itemRefs = useRef({});
101
- const widthCache = useRef({});
102
- const [available, setAvailable] = useState(Number.POSITIVE_INFINITY);
103
- const [count, setCount] = useState(items.length);
179
+ var sameFit = (a, b) => a.folded.join("\n") === b.folded.join("\n") && a.overflow.join("\n") === b.overflow.join("\n");
180
+ var isPrimary = (item) => "label" in item && Boolean(item.primary);
181
+ /**
182
+ * Fits the toolbar to its own width. It measures a hidden copy of the actions,
183
+ * each with its label and folded, and caches the widths of element items as
184
+ * they render, so the answer doesn't depend on how the toolbar is folded at the
185
+ * moment and settles within one paint.
186
+ */
187
+ function useToolbarFit(root, copy, groups, right) {
188
+ const [fit, setFit] = useState(FITS_ALL);
189
+ const elementWidths = useRef(/* @__PURE__ */ new Map());
190
+ const measure = useRef(() => {});
191
+ measure.current = () => {
192
+ const available = root?.clientWidth ?? 0;
193
+ if (!root || !copy || available === 0) {
194
+ setFit((prev) => sameFit(prev, FITS_ALL) ? prev : FITS_ALL);
195
+ return;
196
+ }
197
+ const measured = /* @__PURE__ */ new Map();
198
+ for (const el of copy.querySelectorAll("[data-measure]")) {
199
+ const key = el.dataset["measure"] ?? "";
200
+ const widths = measured.get(key) ?? {};
201
+ widths[el.dataset["fold"] === "folded" ? "folded" : "full"] = el.getBoundingClientRect().width;
202
+ measured.set(key, widths);
203
+ }
204
+ for (const el of root.querySelectorAll("[data-toolbar-element]")) {
205
+ const key = el.dataset["toolbarElement"] ?? "";
206
+ const widths = elementWidths.current.get(key) ?? {};
207
+ widths[fit.folded.includes(key) ? "folded" : "full"] = el.getBoundingClientRect().width;
208
+ elementWidths.current.set(key, widths);
209
+ }
210
+ const widthsOf = (item) => {
211
+ const w = ("element" in item ? elementWidths.current : measured).get(item.key) ?? {};
212
+ const full = w.full ?? w.folded ?? 0;
213
+ return {
214
+ key: item.key,
215
+ full,
216
+ folded: w.folded ?? full,
217
+ primary: isPrimary(item)
218
+ };
219
+ };
220
+ const next = fitToolbar(groups.map((g) => g.map(widthsOf)), right.map(widthsOf), available);
221
+ setFit((prev) => sameFit(prev, next) ? prev : next);
222
+ };
223
+ useLayoutEffect(() => measure.current());
104
224
  useLayoutEffect(() => {
105
- const el = wrapRef.current;
106
- if (!el) return;
107
- const update = () => setAvailable(el.clientWidth);
108
- update();
109
- const observer = new ResizeObserver(update);
110
- observer.observe(el);
225
+ if (!root || !copy) return;
226
+ const observer = new ResizeObserver(() => measure.current());
227
+ observer.observe(root);
228
+ observer.observe(copy);
111
229
  return () => observer.disconnect();
112
- }, []);
113
- useLayoutEffect(() => {
114
- for (const key in itemRefs.current) {
115
- const el = itemRefs.current[key];
116
- if (el) widthCache.current[key] = el.offsetWidth;
117
- }
118
- const next = fitCount(items.map((item) => widthCache.current[item.key] ?? 0), available, ITEM_GAP, MENU_BUTTON_WIDTH);
119
- setCount((prev) => prev === next ? prev : next);
230
+ }, [root, copy]);
231
+ return fit;
232
+ }
233
+ /** The hidden copy `useToolbarFit` measures: each action with its label and
234
+ * folded to its icon, styled as its button is. */
235
+ function MeasuringCopy({ items, ref }) {
236
+ return /* @__PURE__ */ jsx("div", {
237
+ "aria-hidden": true,
238
+ inert: true,
239
+ className: "pointer-events-none invisible absolute top-0 left-0 size-0 overflow-hidden",
240
+ children: /* @__PURE__ */ jsx("div", {
241
+ ref,
242
+ className: "flex w-max gap-1",
243
+ children: items.map((item) => "element" in item ? null : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
244
+ "data-measure": item.key,
245
+ "data-fold": "full",
246
+ className: actionClasses(item, false),
247
+ children: [item.icon, item.label]
248
+ }), item.icon && /* @__PURE__ */ jsx("span", {
249
+ "data-measure": item.key,
250
+ "data-fold": "folded",
251
+ className: actionClasses(item, true),
252
+ children: item.icon
253
+ })] }, item.key))
254
+ })
120
255
  });
121
- const visible = items.slice(0, count);
122
- const overflow = items.slice(count);
123
- return /* @__PURE__ */ jsxs("div", {
124
- ref: wrapRef,
125
- className: "flex-1 min-w-0 flex items-center justify-end gap-1",
126
- children: [visible.map((item) => /* @__PURE__ */ jsx("span", {
127
- ref: (el) => {
128
- itemRefs.current[item.key] = el;
129
- },
256
+ }
257
+ function ItemSlot({ item, folded }) {
258
+ return /* @__PURE__ */ jsx(FoldedContext, {
259
+ value: folded,
260
+ children: "element" in item ? /* @__PURE__ */ jsx("span", {
261
+ "data-toolbar-element": item.key,
130
262
  className: "inline-flex shrink-0",
131
- children: /* @__PURE__ */ jsx(ToolbarItemView, { item })
132
- }, item.key)), overflow.length > 0 && /* @__PURE__ */ jsx(OverflowMenu, { items: overflow })]
263
+ children: item.element
264
+ }) : /* @__PURE__ */ jsx(ToolbarButton, { action: item })
133
265
  });
134
266
  }
135
- /** Pins a detail-page toolbar just below the sticky breadcrumb strip (top-10 matches the strip's 40px height). */
136
- var stickyToolbarClasses = "sticky top-10 z-(--layer-sticky) bg-(--theme-app-bg)";
137
- /** Action groups left, secondary actions right (collapsing into a `…` menu). */
138
- function ActionToolbar({ groups, right = [], sticky = false }) {
267
+ /** Pins a toolbar to the top of the page's scroll area while the content scrolls under it. */
268
+ var stickyToolbarClasses = "sticky top-0 z-(--layer-sticky) bg-(--theme-app-bg)";
269
+ /** A view's controls in one WAI-ARIA toolbar: a single tab stop, with the arrow
270
+ * keys moving between its items, the `…` menu and disabled ones included, so
271
+ * their reasons stay reachable. Action groups sit on the left and `right` holds
272
+ * secondary actions at the end. It stays one row: when it runs out of room
273
+ * labels fold to their icons one at a time, then actions move into a `…` menu,
274
+ * the right-hand ones first and the primary action never. Give each action its
275
+ * own icon, since a folded one is known by it, and mark the one action the
276
+ * view leads to `primary`. */
277
+ function ActionToolbar({ groups, right = [], sticky = false, label }) {
278
+ const { common } = useStrings();
279
+ const [root, setRoot] = useState(null);
280
+ const [copy, setCopy] = useState(null);
139
281
  const visibleGroups = groups.map((group) => group.filter((a) => !a.hidden)).filter((group) => group.length > 0);
140
282
  const visibleRight = right.filter((a) => !a.hidden);
141
- return /* @__PURE__ */ jsxs("div", {
142
- className: cx("flex items-center gap-2.5", sticky && cx("sticky top-10 z-(--layer-sticky) bg-(--theme-app-bg)", "py-1.5")),
143
- children: [visibleGroups.length > 0 && /* @__PURE__ */ jsx("div", {
144
- className: "flex items-center gap-2.5 shrink-0",
145
- children: visibleGroups.map((group, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [i > 0 && /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-(--theme-border)" }), /* @__PURE__ */ jsx("div", {
146
- className: "flex items-center gap-1",
147
- children: group.map((item) => /* @__PURE__ */ jsx(ToolbarItemView, { item }, item.key))
148
- })] }, group[0]?.key ?? i))
149
- }), visibleRight.length > 0 && /* @__PURE__ */ jsx(CollapsibleRight, { items: visibleRight })]
283
+ const all = [...visibleGroups.flat(), ...visibleRight];
284
+ warnIfManyPrimary(all);
285
+ warnIfIconsRepeat(all);
286
+ const fit = useToolbarFit(root, copy, visibleGroups, visibleRight);
287
+ const overflow = new Set(fit.overflow);
288
+ const folded = new Set(fit.folded);
289
+ const shown = (items) => items.filter((item) => !overflow.has(item.key));
290
+ const shownGroups = visibleGroups.map(shown).filter((group) => group.length > 0);
291
+ const shownRight = shown(visibleRight);
292
+ const menu = [...visibleGroups, visibleRight].map((group) => group.filter((item) => overflow.has(item.key))).filter((group) => group.length > 0);
293
+ return /* @__PURE__ */ jsxs(Toolbar.Root, {
294
+ ref: setRoot,
295
+ "aria-label": label ?? common.actions,
296
+ className: cx("relative flex min-w-0 items-center gap-2.5", sticky && cx("sticky top-0 z-(--layer-sticky) bg-(--theme-app-bg)", "py-1.5")),
297
+ children: [/* @__PURE__ */ jsx(MeasuringCopy, {
298
+ ref: setCopy,
299
+ items: all
300
+ }), /* @__PURE__ */ jsxs(PlacementContext, {
301
+ value: "toolbar",
302
+ children: [shownGroups.map((group, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [i > 0 && /* @__PURE__ */ jsx(Toolbar.Separator, {
303
+ orientation: "vertical",
304
+ className: "h-5 w-px shrink-0 bg-(--theme-border)"
305
+ }), /* @__PURE__ */ jsx(Toolbar.Group, {
306
+ className: "flex shrink-0 items-center gap-1",
307
+ children: group.map((item) => /* @__PURE__ */ jsx(ItemSlot, {
308
+ item,
309
+ folded: folded.has(item.key)
310
+ }, item.key))
311
+ })] }, group[0]?.key ?? i)), (shownRight.length > 0 || menu.length > 0) && /* @__PURE__ */ jsxs("div", {
312
+ className: "ml-auto flex shrink-0 items-center gap-1",
313
+ children: [shownRight.map((item) => /* @__PURE__ */ jsx(ItemSlot, {
314
+ item,
315
+ folded: folded.has(item.key)
316
+ }, item.key)), menu.length > 0 && /* @__PURE__ */ jsx(OverflowMenu, { items: menu })]
317
+ })]
318
+ })]
150
319
  });
151
320
  }
321
+ var warnedPrimary = /* @__PURE__ */ new Set();
322
+ /** Warns in development when more than one action is primary: the fill marks
323
+ * the one next step, so two of them mark none. */
324
+ function warnIfManyPrimary(items) {
325
+ if (!isDev()) return;
326
+ const labels = items.flatMap((item) => "label" in item && item.primary ? [item.label] : []);
327
+ const names = labels.map((label) => `"${label}"`).join(", ");
328
+ if (labels.length < 2 || warnedPrimary.has(names)) return;
329
+ warnedPrimary.add(names);
330
+ console.warn(`ActionToolbar: ${names} are all primary; mark one action at most.`);
331
+ }
332
+ var warnedIcons = /* @__PURE__ */ new Set();
333
+ /** Warns in development when two actions share an icon: folded to their icons
334
+ * in a narrow toolbar, they would look the same. */
335
+ function warnIfIconsRepeat(items) {
336
+ if (!isDev()) return;
337
+ const byIcon = /* @__PURE__ */ new Map();
338
+ for (const item of items) {
339
+ if ("element" in item || !isValidElement(item.icon) || typeof item.icon.type === "string") continue;
340
+ byIcon.set(item.icon.type, [...byIcon.get(item.icon.type) ?? [], item.label]);
341
+ }
342
+ for (const labels of byIcon.values()) {
343
+ const names = labels.map((label) => `"${label}"`).join(", ");
344
+ if (labels.length < 2 || warnedIcons.has(names)) continue;
345
+ warnedIcons.add(names);
346
+ console.warn(`ActionToolbar: ${names} share an icon, so they look the same once their labels fold; give each its own.`);
347
+ }
348
+ }
152
349
  //#endregion
153
- export { ActionToolbar, ToolbarButton, stickyToolbarClasses };
350
+ export { ActionToolbar, ToolbarButton, fitToolbar, stickyToolbarClasses };
@@ -1,40 +1,29 @@
1
1
  import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
2
2
  import type { OpenMenu } from '../components/ContextMenu.js';
3
3
  import { type MenuEntry } from '../components/MenuEntries.js';
4
- import { type PageHeaderBreadcrumb } from '../components/PageHeader.js';
5
4
  import type { ListView, ListViewChrome } from './listView.js';
6
5
  import { type RowAction } from './rowMenu.js';
7
- /** Negative offsets (admin/org) cancel the shell's pt-4 so rows don't peek through the gap above the pinned header. */
8
- declare const SECTION_STICKY_TOP: {
9
- readonly main: 'top-0';
10
- readonly kubernetes: 'top-0';
11
- readonly admin: '-top-4';
12
- readonly org: '-top-4';
13
- };
14
- export type ListSection = keyof typeof SECTION_STICKY_TOP;
15
6
  export declare function ListRowActionsProvider({ view, children, }: {
16
7
  view?: ListViewChrome | undefined;
17
8
  children: ReactNode;
18
9
  }): import("react").JSX.Element;
19
10
  export interface ListPageProps {
20
- section?: ListSection;
21
- /** Force edge-to-edge negative margin for a main-shell page nested under a padded route layout. */
22
- shellPadded?: boolean;
23
- /** Rendered inside a route layout that already owns the page header (e.g. a
24
- * detail page's tab body). Skips this page's own header so the global
25
- * breadcrumb strip stays visible, keeping only the toolbar. */
26
- embedded?: boolean;
27
- breadcrumbs?: PageHeaderBreadcrumb[] | undefined;
28
- title?: ReactNode;
29
- titleAction?: ReactNode;
30
- actions?: ReactNode;
11
+ /** Reaches past the host's content padding (`p-4`) to the container's
12
+ * edges, for a host whose page container is padded. A list runs edge to
13
+ * edge so its rows' hover and dividers span the page. */
14
+ bleed?: boolean | undefined;
15
+ /** The list's search, filters and view controls, above the rows. The page's
16
+ * trail and actions go in its `PageHeader`. */
31
17
  toolbar?: ReactNode;
32
18
  /** Omit on pages without a list view — rows show all actions by default. */
33
19
  view?: ListViewChrome | undefined;
34
20
  children: ReactNode;
35
21
  }
36
- export declare function ListPage({ section, shellPadded, embedded, breadcrumbs, title, titleAction, actions, toolbar, view, children, }: ListPageProps): import("react").JSX.Element;
22
+ /** A list filling the page: its toolbar stays put while only the rows scroll. */
23
+ export declare function ListPage({ bleed, toolbar, view, children }: ListPageProps): import("react").JSX.Element;
37
24
  export declare function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children, }: {
25
+ /** Where the row goes. Its `NameCell` with the same href becomes a link
26
+ * covering the row; a click the link doesn't catch still goes there. */
38
27
  href: string | null;
39
28
  /** Plain-click handler for rows that open something in place instead of navigating. */
40
29
  onActivate?: (() => void) | undefined;
@@ -53,7 +42,11 @@ export declare function ListRow({ href, onActivate, items, goTo, openMenu, pinne
53
42
  leading?: ReactNode;
54
43
  children: ReactNode;
55
44
  }): import("react").JSX.Element;
56
- /** Name column min-width is by role, not position, so a leading checkbox doesn't shift it. */
45
+ /** Name column min-width is by role, not position, so a leading checkbox
46
+ * doesn't shift it. A column with an `orderBy` the view offers sorts by it.
47
+ * While the view's column headers are off, the header row collapses: its
48
+ * cells still size the columns, but nobody sees or hears them, as with any
49
+ * other display option that is off. */
57
50
  export declare function ListColumns<T>({ view }: {
58
51
  view: ListView<T>;
59
52
  }): import("react").JSX.Element;
@@ -62,4 +55,3 @@ export declare namespace ListColumns {
62
55
  }
63
56
  /** Stable `goTo` for whole-row navigation (`rowNavProps`, `ListRow`). */
64
57
  export declare function useListNavigate(): (to: string) => void;
65
- export {};