@parallelworks/ui 0.51.0 → 0.52.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 (227) hide show
  1. package/README.md +155 -14
  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 +12 -0
  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 +81 -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 +21 -9
  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 +15 -5
  67. package/dist/components/Field.js +40 -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 +4 -1
  71. package/dist/components/InfoTip.js +2 -1
  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/MenuEntries.d.ts +4 -0
  76. package/dist/components/MenuEntries.js +11 -12
  77. package/dist/components/Meter.js +5 -4
  78. package/dist/components/Modal.d.ts +28 -4
  79. package/dist/components/Modal.js +37 -14
  80. package/dist/components/Overview.d.ts +20 -12
  81. package/dist/components/Overview.js +14 -9
  82. package/dist/components/PageHeader.d.ts +47 -65
  83. package/dist/components/PageHeader.js +275 -153
  84. package/dist/components/PageHeaderAction.d.ts +67 -0
  85. package/dist/components/PageHeaderAction.js +107 -0
  86. package/dist/components/Pagination.js +6 -16
  87. package/dist/components/Popover.js +3 -2
  88. package/dist/components/Provider.d.ts +77 -19
  89. package/dist/components/Provider.js +139 -130
  90. package/dist/components/Radio.js +2 -1
  91. package/dist/components/RefreshButton.js +7 -8
  92. package/dist/components/RequiredMark.js +1 -1
  93. package/dist/components/Section.js +1 -1
  94. package/dist/components/Select.d.ts +4 -9
  95. package/dist/components/Select.js +24 -11
  96. package/dist/components/SettingModeOption.d.ts +1 -1
  97. package/dist/components/SettingModeOption.js +7 -1
  98. package/dist/components/SettingsCard.d.ts +5 -3
  99. package/dist/components/SettingsCard.js +27 -20
  100. package/dist/components/Slider.js +1 -1
  101. package/dist/components/StatusBadge.js +2 -2
  102. package/dist/components/Switch.js +5 -4
  103. package/dist/components/Table.d.ts +30 -20
  104. package/dist/components/Table.js +50 -73
  105. package/dist/components/Tabs.d.ts +3 -7
  106. package/dist/components/Tabs.js +6 -10
  107. package/dist/components/ThemeEditor.js +18 -8
  108. package/dist/components/Toast.d.ts +11 -3
  109. package/dist/components/Toast.js +45 -20
  110. package/dist/components/buttonClasses.js +1 -1
  111. package/dist/components/comboboxItems.d.ts +4 -0
  112. package/dist/components/comboboxItems.js +6 -1
  113. package/dist/components/dismiss.d.ts +12 -0
  114. package/dist/components/dismiss.js +19 -0
  115. package/dist/components/fieldBox.d.ts +4 -0
  116. package/dist/components/fieldBox.js +7 -3
  117. package/dist/components/floating.d.ts +3 -3
  118. package/dist/components/floating.js +1 -1
  119. package/dist/components/markdownCodeBreaks.js +3 -4
  120. package/dist/components/pageHeaderStore.d.ts +41 -0
  121. package/dist/components/pageHeaderStore.js +67 -0
  122. package/dist/components/pathBreaks.d.ts +8 -0
  123. package/dist/components/pathBreaks.js +28 -0
  124. package/dist/components/segmented.js +1 -1
  125. package/dist/components/shortcutKey.d.ts +2 -1
  126. package/dist/components/shortcutKey.js +19 -2
  127. package/dist/components/splitter.js +1 -1
  128. package/dist/components/useCssIsDark.js +40 -18
  129. package/dist/components/useTheme.js +9 -10
  130. package/dist/dev.d.ts +9 -0
  131. package/dist/dev.js +14 -0
  132. package/dist/editor/Editor.d.ts +3 -2
  133. package/dist/editor/Editor.js +11 -2
  134. package/dist/editor/EditorField.d.ts +2 -1
  135. package/dist/editor/EditorField.js +11 -29
  136. package/dist/editor/FormCodePanel.d.ts +7 -3
  137. package/dist/editor/FormCodePanel.js +14 -7
  138. package/dist/editor/Monaco.d.ts +14 -5
  139. package/dist/editor/Monaco.js +163 -112
  140. package/dist/editor/index.d.ts +5 -5
  141. package/dist/editor/index.js +3 -3
  142. package/dist/editor/themes.d.ts +8 -5
  143. package/dist/editor/themes.js +58 -30
  144. package/dist/editor/yaml.d.ts +6 -2
  145. package/dist/editor/yaml.js +11 -2
  146. package/dist/file-explorer/FileExplorer.js +143 -124
  147. package/dist/file-explorer/FileExplorerContext.js +1 -1
  148. package/dist/file-explorer/FilePreview/StreamedBadge.js +3 -1
  149. package/dist/file-explorer/FilePreview/Viewer.js +2 -2
  150. package/dist/file-explorer/FilePreview/index.js +4 -4
  151. package/dist/file-explorer/FileRow.js +1 -1
  152. package/dist/file-explorer/FileRowsWindow.js +34 -3
  153. package/dist/file-explorer/FileTable.js +3 -1
  154. package/dist/file-explorer/TreeView.js +37 -14
  155. package/dist/file-explorer/helpers.d.ts +2 -1
  156. package/dist/file-explorer/helpers.js +21 -20
  157. package/dist/file-explorer/toasts.js +27 -19
  158. package/dist/file-explorer/treeRows.d.ts +6 -0
  159. package/dist/file-explorer/treeRows.js +19 -1
  160. package/dist/file-explorer/useNearEnd.js +3 -6
  161. package/dist/form/Fields/CheckboxGroupField.js +12 -5
  162. package/dist/form/Fields/DurationField.js +1 -1
  163. package/dist/form/Fields/LabelField.js +1 -1
  164. package/dist/form/Fields/ObjectField.d.ts +1 -1
  165. package/dist/form/Fields/ObjectField.js +3 -2
  166. package/dist/form/Fields/StringField.js +16 -29
  167. package/dist/form/Fields/TextAreaField.js +3 -4
  168. package/dist/form/Form.js +3 -8
  169. package/dist/form/Wizard/WizardNavigation.js +0 -3
  170. package/dist/form/Wizard/utils.js +1 -1
  171. package/dist/form/fieldFrame.d.ts +1 -1
  172. package/dist/form/fieldFrame.js +2 -2
  173. package/dist/form/types/fieldComponentTypes.d.ts +2 -1
  174. package/dist/graph/AnnotationBanner.d.ts +1 -2
  175. package/dist/graph/AnnotationBanner.js +30 -32
  176. package/dist/graph/Connectors.js +4 -1
  177. package/dist/graph/DependencyGraph.js +2 -2
  178. package/dist/graph/GraphEditor.js +11 -9
  179. package/dist/graph/GraphEditorDialogs.js +1 -1
  180. package/dist/graph/GraphNode.js +7 -4
  181. package/dist/graph/InputDialog.js +1 -1
  182. package/dist/graph/InputsEditor.js +14 -11
  183. package/dist/graph/MatrixGroup.js +9 -3
  184. package/dist/graph/Reveal.js +18 -10
  185. package/dist/graph/ShortcutsButton.d.ts +11 -2
  186. package/dist/graph/ShortcutsButton.js +14 -3
  187. package/dist/graph/TreeView.js +35 -18
  188. package/dist/graph/WorkflowSettingsDialog.js +1 -1
  189. package/dist/graph/editorFields.js +1 -1
  190. package/dist/graph/editorRows.js +16 -10
  191. package/dist/graph/editorStrings.d.ts +7 -0
  192. package/dist/graph/editorStrings.js +8 -1
  193. package/dist/graph/inputRefs.js +1 -1
  194. package/dist/graph/settingsViews.js +11 -18
  195. package/dist/graph/stepWith.js +1 -1
  196. package/dist/icons.d.ts +2 -0
  197. package/dist/icons.js +4 -2
  198. package/dist/index.d.ts +12 -12
  199. package/dist/index.js +16 -16
  200. package/dist/list/ActionToolbar.d.ts +42 -6
  201. package/dist/list/ActionToolbar.js +297 -101
  202. package/dist/list/ListPage.d.ts +15 -23
  203. package/dist/list/ListPage.js +42 -43
  204. package/dist/list/ListTable.d.ts +32 -7
  205. package/dist/list/ListTable.js +48 -12
  206. package/dist/list/ListViewControls.js +1 -1
  207. package/dist/list/UserHoverCard.js +7 -15
  208. package/dist/list/index.d.ts +2 -2
  209. package/dist/list/index.js +2 -2
  210. package/dist/list/listSearch.js +12 -10
  211. package/dist/list/listView.d.ts +10 -1
  212. package/dist/list/listView.js +15 -1
  213. package/dist/logviewer/LogViewer.d.ts +4 -2
  214. package/dist/logviewer/LogViewer.js +87 -69
  215. package/dist/logviewer/logviewer.css +3 -2
  216. package/dist/reducedMotion.d.ts +5 -0
  217. package/dist/reducedMotion.js +20 -0
  218. package/dist/safeUrl.d.ts +1 -1
  219. package/dist/safeUrl.js +4 -2
  220. package/dist/styles.css +1 -1
  221. package/package.json +1 -1
  222. package/dist/components/Breadcrumbs.d.ts +0 -37
  223. package/dist/components/Breadcrumbs.js +0 -90
  224. package/dist/components/CopyToClipboard.d.ts +0 -17
  225. package/dist/components/CopyToClipboard.js +0 -66
  226. package/dist/components/Sort.d.ts +0 -9
  227. package/dist/components/Sort.js +0 -42
@@ -1,181 +1,303 @@
1
1
  import { cx } from "../cx.js";
2
- import { AddIcon, ChevronRightIcon, EditIcon, MoreIcon } from "../icons.js";
3
- import { keyedByContent } from "./keys.js";
4
2
  import { buttonClasses } from "./buttonClasses.js";
3
+ import { ChevronRightIcon, MoreIcon } from "../icons.js";
4
+ import { keyedByContent } from "./keys.js";
5
5
  import { Tooltip } from "./Tooltip.js";
6
- import { useLink, useSlots, useStrings } from "./Provider.js";
6
+ import { useSlots, useStrings } from "./Provider.js";
7
+ import { AppLink } from "./AppLink.js";
7
8
  import { ActionMenu } from "./ActionMenu.js";
8
- import { useBreadcrumb } from "./Breadcrumbs.js";
9
+ import { Skeleton } from "./Skeleton.js";
10
+ import { PageHeaderStore } from "./pageHeaderStore.js";
9
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
- import { Fragment as Fragment$1, useLayoutEffect } from "react";
12
+ import { createContext, use, useCallback, useLayoutEffect, useState, useSyncExternalStore } from "react";
11
13
  import { createPortal } from "react-dom";
12
14
  //#region src/components/PageHeader.tsx
13
- var headerActionButtonClasses = buttonClasses({
14
- variant: "ghost",
15
- size: "xs",
16
- iconOnly: true
17
- });
18
- var headerAddButtonClasses = buttonClasses({
19
- variant: "outline",
20
- size: "xs"
21
- });
22
- /** With menu, the plus opens a chooser; otherwise navigates to or calls the
23
- * single action. `word` is the visible verb; `label` stays the full action for
24
- * the tooltip and screen readers. */
25
- function HeaderAddButton({ href, onClick, label, word = "add", menu, disabled = false, disabledHint }) {
26
- const { common } = useStrings();
27
- const Link = useLink();
28
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AddIcon, { className: "w-3 h-3" }), /* @__PURE__ */ jsx("span", { children: word === "create" ? common.create : common.add })] });
29
- if (menu) return /* @__PURE__ */ jsx(ActionMenu, {
30
- items: menu,
31
- label,
32
- triggerClassName: headerAddButtonClasses,
33
- children: content
15
+ var StoreContext = createContext(null);
16
+ /**
17
+ * Holds the header the current page declares, for the `PageHeaderBar` the
18
+ * shell renders. Wrap the shell, above both the bar and the routes.
19
+ */
20
+ function PageHeaderProvider({ children }) {
21
+ const [store] = useState(() => new PageHeaderStore());
22
+ return /* @__PURE__ */ jsx(StoreContext, {
23
+ value: store,
24
+ children
34
25
  });
35
- if (href) return /* @__PURE__ */ jsx(Tooltip, {
36
- content: label,
37
- children: /* @__PURE__ */ jsx(Link, {
38
- href,
39
- "aria-label": label,
40
- className: headerAddButtonClasses,
41
- children: content
42
- })
26
+ }
27
+ var renderOrder = 0;
28
+ var subscribeNever = () => () => {};
29
+ function createRegionNodes() {
30
+ if (typeof document === "undefined") return null;
31
+ const node = () => {
32
+ const el = document.createElement("div");
33
+ el.style.display = "contents";
34
+ return el;
35
+ };
36
+ return {
37
+ titleActions: node(),
38
+ actions: node(),
39
+ toolbar: node()
40
+ };
41
+ }
42
+ /**
43
+ * Declares the page's header: its breadcrumbs, title actions, actions and
44
+ * toolbar. Under a `PageHeaderProvider` it renders nothing in place: the
45
+ * shell's `PageHeaderBar` shows it, while the actions stay in the page's React
46
+ * tree, with its context and state. Without a provider it renders the bar
47
+ * itself, for a page with no shell.
48
+ *
49
+ * Any component may render one, such as a form adding its Save button: the bar
50
+ * shows the actions of all of them, outermost first, and the trail of the last
51
+ * one rendered that has one, so a page's trail replaces its layout's.
52
+ */
53
+ function PageHeader(props) {
54
+ const store = use(StoreContext);
55
+ if (!store) return props.hidden ? null : /* @__PURE__ */ jsx(HeaderLayout, {
56
+ breadcrumbs: props.breadcrumbs ?? [],
57
+ titleActions: props.titleActions,
58
+ actions: props.actions,
59
+ toolbar: props.toolbar
43
60
  });
44
- const hint = disabled ? disabledHint : void 0;
45
- return /* @__PURE__ */ jsx(Tooltip, {
46
- content: hint ?? label,
47
- children: /* @__PURE__ */ jsx("button", {
48
- type: "button",
49
- onClick: disabled ? void 0 : onClick,
50
- "aria-label": hint ? `${label}. ${hint}` : label,
51
- "aria-disabled": disabled || void 0,
52
- className: headerAddButtonClasses,
53
- children: content
54
- })
61
+ return /* @__PURE__ */ jsx(DeclaredHeader, {
62
+ store,
63
+ ...props
55
64
  });
56
65
  }
57
- /** The header's "…" menu of actions on the page's subject. */
58
- function HeaderActionMenu({ items, label }) {
59
- const { common } = useStrings();
60
- if (items.length === 0) return null;
61
- return /* @__PURE__ */ jsx(ActionMenu, {
62
- items,
63
- label: label ?? common.actions,
64
- triggerClassName: headerActionButtonClasses,
65
- children: /* @__PURE__ */ jsx(MoreIcon, { className: "w-3.5 h-3.5" })
66
+ function DeclaredHeader({ store, breadcrumbs, titleActions, actions, toolbar, hidden = false }) {
67
+ const [order] = useState(() => ++renderOrder);
68
+ const [nodes] = useState(createRegionNodes);
69
+ const client = useSyncExternalStore(subscribeNever, () => true, () => false);
70
+ const fills = {
71
+ titleActions: present(titleActions),
72
+ actions: present(actions),
73
+ toolbar: present(toolbar)
74
+ };
75
+ useLayoutEffect(() => {
76
+ store.set(order, {
77
+ breadcrumbs,
78
+ hidden,
79
+ nodes,
80
+ fills
81
+ });
66
82
  });
83
+ useLayoutEffect(() => () => store.delete(order), [store, order]);
84
+ if (!nodes || !client) return null;
85
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ fills.titleActions && createPortal(titleActions, nodes.titleActions),
87
+ fills.actions && createPortal(actions, nodes.actions),
88
+ fills.toolbar && createPortal(toolbar, nodes.toolbar)
89
+ ] });
67
90
  }
68
- function HeaderEditButton({ href, onClick, label: labelProp, disabled, tooltip }) {
69
- const Link = useLink();
70
- const { common } = useStrings();
71
- const label = labelProp ?? common.edit;
72
- return /* @__PURE__ */ jsx(Tooltip, {
73
- content: tooltip ?? label,
74
- children: href && !disabled ? /* @__PURE__ */ jsx(Link, {
75
- href,
76
- "aria-label": label,
77
- className: headerActionButtonClasses,
78
- children: /* @__PURE__ */ jsx(EditIcon, { className: "w-3 h-3" })
79
- }) : /* @__PURE__ */ jsx("button", {
80
- type: "button",
81
- onClick,
82
- disabled,
83
- "aria-label": label,
84
- className: headerActionButtonClasses,
85
- children: /* @__PURE__ */ jsx(EditIcon, { className: "w-3 h-3" })
86
- })
91
+ function present(node) {
92
+ return node !== void 0 && node !== null && node !== false && node !== "";
93
+ }
94
+ /**
95
+ * The bar showing what the current page declares with `PageHeader`. The shell
96
+ * renders one, inside a `PageHeaderProvider`, wherever its chrome puts it. It
97
+ * renders nothing while the page declares nothing.
98
+ */
99
+ function PageHeaderBar({ compact = false, persistent = false, className }) {
100
+ const store = use(StoreContext);
101
+ if (!store) throw new Error("PageHeaderBar needs a PageHeaderProvider above it");
102
+ const header = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
103
+ const empty = header.breadcrumbs.length === 0 && header.titleActions.length === 0 && header.actions.length === 0 && header.toolbar.length === 0;
104
+ if (header.hidden || empty && !persistent) return null;
105
+ return /* @__PURE__ */ jsx(HeaderLayout, {
106
+ breadcrumbs: header.breadcrumbs,
107
+ titleActions: header.titleActions.length > 0 && /* @__PURE__ */ jsx(Mounts, { nodes: header.titleActions }),
108
+ actions: header.actions.length > 0 && /* @__PURE__ */ jsx(Mounts, { nodes: header.actions }),
109
+ toolbar: header.toolbar.length > 0 && /* @__PURE__ */ jsx(Mounts, { nodes: header.toolbar }),
110
+ compact,
111
+ className
87
112
  });
88
113
  }
89
- /** Renders nothing when the modern breadcrumb strip isn't mounted (classic chrome or self-headered pages). */
90
- function BreadcrumbsTitleAction({ children }) {
91
- const { titleActionSlot } = useBreadcrumb();
92
- if (!titleActionSlot) return null;
93
- return createPortal(children, titleActionSlot);
114
+ var nodeKeys = /* @__PURE__ */ new WeakMap();
115
+ var nextNodeKey = 0;
116
+ function nodeKey(node) {
117
+ let key = nodeKeys.get(node);
118
+ if (key === void 0) {
119
+ key = ++nextNodeKey;
120
+ nodeKeys.set(node, key);
121
+ }
122
+ return key;
94
123
  }
95
- function BreadcrumbsRightAction({ children }) {
96
- const { rightActionSlot } = useBreadcrumb();
97
- if (!rightActionSlot) return null;
98
- return createPortal(children, rightActionSlot);
124
+ function Mounts({ nodes }) {
125
+ return nodes.map((node) => /* @__PURE__ */ jsx(Mount, { node }, nodeKey(node)));
99
126
  }
100
- var breadcrumbsActionClasses = "flex cursor-pointer items-center gap-1 text-xs whitespace-nowrap hover:not-disabled:underline disabled:cursor-not-allowed disabled:opacity-50";
101
- function BreadcrumbsActionButton({ icon: Icon, label, ariaLabel, href, onClick, disabled, tooltip, destructive = false }) {
102
- const Link = useLink();
103
- const className = cx(breadcrumbsActionClasses, destructive ? "text-(--theme-error-text)" : "text-(--theme-link)");
104
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, { className: "w-3 h-3" }), label] });
105
- return /* @__PURE__ */ jsx(Tooltip, {
106
- content: tooltip,
107
- children: href && !disabled ? /* @__PURE__ */ jsx(Link, {
108
- href,
109
- className,
110
- "aria-label": ariaLabel,
111
- children: content
112
- }) : /* @__PURE__ */ jsx("button", {
113
- type: "button",
114
- onClick,
115
- disabled,
116
- className,
117
- "aria-label": ariaLabel,
118
- children: content
119
- })
127
+ /** Moves a declaration's region node into the bar. React owns the span and
128
+ * keeps it in order; the node inside is the page's. */
129
+ function Mount({ node }) {
130
+ const attach = useCallback((span) => {
131
+ if (span && node.parentNode !== span) span.append(node);
132
+ }, [node]);
133
+ return /* @__PURE__ */ jsx("span", {
134
+ ref: attach,
135
+ className: "contents"
120
136
  });
121
137
  }
122
- function PageHeader({ breadcrumbs, title, titleAction, actions, toolbar, className, sticky = true, stickyTopClassName = "top-0" }) {
123
- const { setHidden } = useBreadcrumb();
124
- const Link = useLink();
138
+ function HeaderLayout({ breadcrumbs, titleActions, actions, toolbar, compact = false, className }) {
125
139
  const { breadcrumbPin, navToggle } = useSlots();
126
- const { common } = useStrings();
127
- useLayoutEffect(() => {
128
- setHidden(true);
129
- return () => setHidden(false);
130
- }, [setHidden]);
131
- const crumbs = [...breadcrumbs ?? []];
132
- const lastLabel = crumbs[crumbs.length - 1]?.label;
133
- if (title !== null && title !== void 0 && title !== lastLabel) crumbs.push({ label: title });
134
- const crumbRow = crumbs.length > 0 && /* @__PURE__ */ jsxs("div", {
135
- className: "flex items-center gap-1.5 min-w-0",
136
- children: [
137
- /* @__PURE__ */ jsx("nav", {
138
- "aria-label": common.breadcrumb,
139
- className: "flex items-center gap-2 min-w-0 text-[13px]",
140
- children: keyedByContent(crumbs, (c) => c.href ?? (typeof c.label === "string" ? c.label : "crumb")).map(({ key, item: crumb }, idx) => {
141
- if (idx === crumbs.length - 1) return /* @__PURE__ */ jsx("h1", {
142
- className: "m-0 text-[13px] font-medium leading-none text-(--theme-app) truncate",
143
- children: crumb.label
144
- }, key);
145
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [crumb.href ? /* @__PURE__ */ jsx(Link, {
146
- href: crumb.href,
147
- className: "text-(--theme-muted-text-color) hover:text-(--theme-app) transition-colors cursor-pointer whitespace-nowrap",
148
- children: crumb.label
149
- }) : /* @__PURE__ */ jsx("span", {
150
- className: "text-(--theme-muted-text-color) whitespace-nowrap",
151
- children: crumb.label
152
- }), /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3 h-3 opacity-60 shrink-0 text-(--theme-muted-text-color)" })] }, key);
153
- })
154
- }),
155
- titleAction,
156
- breadcrumbPin?.({ crumbs })
157
- ]
158
- });
159
- const actionCluster = actions && /* @__PURE__ */ jsx("div", {
160
- className: "flex items-center gap-1.5 shrink-0",
161
- children: actions
162
- });
163
- const toggle = navToggle?.();
164
- const hasLeading = !!crumbRow || !!toggle;
165
- const leading = hasLeading ? /* @__PURE__ */ jsxs("div", {
166
- className: "flex items-center gap-2 min-w-0",
167
- children: [toggle, crumbRow]
168
- }) : /* @__PURE__ */ jsx("div", {});
140
+ const hasTrail = breadcrumbs.length > 0;
169
141
  return /* @__PURE__ */ jsxs("div", {
170
- className: cx(sticky && cx("sticky z-(--layer-header)", stickyTopClassName), "w-full bg-(--theme-app-bg)", className),
142
+ className: cx("@container w-full shrink-0 bg-(--theme-app-bg)", className),
171
143
  children: [/* @__PURE__ */ jsxs("div", {
172
- className: cx("flex items-center justify-between gap-6 px-4 h-11", hasLeading && "border-b border-(--theme-border)"),
173
- children: [leading, actionCluster]
174
- }), toolbar && /* @__PURE__ */ jsx("div", {
144
+ className: cx("flex items-center justify-between gap-4 border-b border-(--theme-border) px-4", compact ? "h-8" : "h-11"),
145
+ children: [/* @__PURE__ */ jsxs("div", {
146
+ className: "flex min-w-0 flex-1 items-center gap-2",
147
+ children: [
148
+ navToggle?.(),
149
+ hasTrail && /* @__PURE__ */ jsx(BreadcrumbTrail, { crumbs: breadcrumbs }),
150
+ present(titleActions) && /* @__PURE__ */ jsx("div", {
151
+ className: "flex shrink-0 items-center gap-0.5",
152
+ children: titleActions
153
+ }),
154
+ hasTrail && breadcrumbPin?.({ crumbs: breadcrumbs })
155
+ ]
156
+ }), present(actions) && /* @__PURE__ */ jsx("div", {
157
+ className: "flex shrink-0 items-center gap-1",
158
+ children: actions
159
+ })]
160
+ }), present(toolbar) && /* @__PURE__ */ jsx("div", {
175
161
  className: "px-4",
176
162
  children: toolbar
177
163
  })]
178
164
  });
179
165
  }
166
+ var crumbText = "block truncate text-(--theme-muted-text-color)";
167
+ var crumbLink = "rounded-sm transition-colors hover:text-(--theme-app) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-link)";
168
+ function Separator() {
169
+ return /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3 shrink-0 text-(--theme-muted-text-color) opacity-60" });
170
+ }
171
+ var FOLD_WIDTH = 48;
172
+ var PARENT_MAX_WIDTH = 144;
173
+ var TITLE_MIN_WIDTH = 128;
174
+ /**
175
+ * Fits the trail to the room its row leaves it. It measures a hidden copy of
176
+ * the trail at its natural width against the row's width less its other items,
177
+ * so the answer doesn't depend on how the trail is folded at the moment.
178
+ */
179
+ function useTrailFit(nav, natural) {
180
+ const [fit, setFit] = useState("all");
181
+ useLayoutEffect(() => {
182
+ const row = nav?.parentElement;
183
+ if (!nav || !natural || !row) return;
184
+ const measure = () => {
185
+ const gap = Number.parseFloat(getComputedStyle(row).columnGap) || 0;
186
+ let room = row.clientWidth;
187
+ for (const child of row.children) if (child !== nav) room -= child.getBoundingClientRect().width + gap;
188
+ if (natural.scrollWidth <= room) {
189
+ setFit("all");
190
+ return;
191
+ }
192
+ const steps = [...natural.children];
193
+ const width = (el) => el?.getBoundingClientRect().width ?? 0;
194
+ const parent = Math.min(width(steps[steps.length - 2]), PARENT_MAX_WIDTH);
195
+ const title = Math.min(width(steps[steps.length - 1]), TITLE_MIN_WIDTH);
196
+ setFit(FOLD_WIDTH + parent + title <= room ? "parent" : "none");
197
+ };
198
+ measure();
199
+ const observer = new ResizeObserver(measure);
200
+ observer.observe(row);
201
+ observer.observe(natural);
202
+ return () => observer.disconnect();
203
+ }, [nav, natural]);
204
+ return fit;
205
+ }
206
+ function BreadcrumbTrail({ crumbs }) {
207
+ const { common } = useStrings();
208
+ const [nav, setNav] = useState(null);
209
+ const [natural, setNatural] = useState(null);
210
+ const fit = useTrailFit(nav, natural);
211
+ const current = crumbs[crumbs.length - 1];
212
+ const ancestors = crumbs.slice(0, -1);
213
+ const folded = fit === "none" ? ancestors : fit === "parent" ? ancestors.slice(0, -1) : [];
214
+ const shown = ancestors.slice(folded.length);
215
+ return /* @__PURE__ */ jsxs("nav", {
216
+ ref: setNav,
217
+ "aria-label": common.breadcrumb,
218
+ className: "relative min-w-0",
219
+ children: [/* @__PURE__ */ jsxs("ol", {
220
+ className: "m-0 flex min-w-0 list-none items-center gap-1.5 p-0 text-[13px] leading-none",
221
+ children: [
222
+ folded.length > 0 && /* @__PURE__ */ jsxs("li", {
223
+ className: "flex shrink-0 items-center gap-1.5",
224
+ children: [/* @__PURE__ */ jsx(ActionMenu, {
225
+ label: common.showFullPath,
226
+ align: "start",
227
+ items: folded.map((crumb) => crumb.href ? {
228
+ label: crumb.label,
229
+ href: crumb.href
230
+ } : {
231
+ label: crumb.label,
232
+ disabled: true,
233
+ onClick: () => {}
234
+ }),
235
+ triggerClassName: buttonClasses({
236
+ variant: "ghost",
237
+ size: "xs",
238
+ iconOnly: true
239
+ }),
240
+ children: /* @__PURE__ */ jsx(MoreIcon, {})
241
+ }), /* @__PURE__ */ jsx(Separator, {})]
242
+ }),
243
+ keyedByContent(shown, (c) => c.href ?? c.label).map(({ key, item: crumb }) => /* @__PURE__ */ jsxs("li", {
244
+ className: cx("flex items-center gap-1.5", folded.length > 0 ? "min-w-0 max-w-36 shrink-0" : "min-w-0"),
245
+ children: [crumb.label === "" ? /* @__PURE__ */ jsx(CrumbPlaceholder, {}) : /* @__PURE__ */ jsx(Tooltip, {
246
+ content: crumb.label,
247
+ overflowOnly: true,
248
+ children: crumb.href ? /* @__PURE__ */ jsx(AppLink, {
249
+ href: crumb.href,
250
+ className: cx(crumbText, crumbLink, "max-w-48"),
251
+ children: crumb.label
252
+ }) : /* @__PURE__ */ jsx("span", {
253
+ className: cx(crumbText, "max-w-48"),
254
+ children: crumb.label
255
+ })
256
+ }), /* @__PURE__ */ jsx(Separator, {})]
257
+ }, key)),
258
+ current && /* @__PURE__ */ jsx("li", {
259
+ className: "flex min-w-0 items-center",
260
+ children: current.label === "" ? /* @__PURE__ */ jsx("h1", {
261
+ "aria-current": "page",
262
+ className: "m-0",
263
+ children: /* @__PURE__ */ jsx(CrumbPlaceholder, {})
264
+ }) : /* @__PURE__ */ jsx(Tooltip, {
265
+ content: current.label,
266
+ overflowOnly: true,
267
+ children: /* @__PURE__ */ jsx("h1", {
268
+ "aria-current": "page",
269
+ className: "m-0 truncate text-[13px] font-medium leading-none text-(--theme-app)",
270
+ children: current.label
271
+ })
272
+ })
273
+ })
274
+ ]
275
+ }), /* @__PURE__ */ jsx("div", {
276
+ ref: setNatural,
277
+ "aria-hidden": true,
278
+ className: "invisible absolute top-0 left-0 flex items-center gap-1.5 whitespace-nowrap text-[13px] leading-none",
279
+ children: crumbs.map((crumb, i) => /* @__PURE__ */ jsxs("span", {
280
+ className: "flex items-center gap-1.5",
281
+ children: [/* @__PURE__ */ jsx("span", {
282
+ className: cx(i < crumbs.length - 1 && "max-w-48 truncate", i === crumbs.length - 1 && "font-medium"),
283
+ children: crumb.label || " ".repeat(6)
284
+ }), i < crumbs.length - 1 && /* @__PURE__ */ jsx(Separator, {})]
285
+ }, i))
286
+ })]
287
+ });
288
+ }
289
+ function CrumbPlaceholder() {
290
+ const { common } = useStrings();
291
+ return /* @__PURE__ */ jsxs("span", {
292
+ className: "flex items-center",
293
+ children: [/* @__PURE__ */ jsx(Skeleton, {
294
+ width: "6rem",
295
+ height: "0.8125rem"
296
+ }), /* @__PURE__ */ jsx("span", {
297
+ className: "sr-only",
298
+ children: common.loading
299
+ })]
300
+ });
301
+ }
180
302
  //#endregion
181
- export { BreadcrumbsActionButton, BreadcrumbsRightAction, BreadcrumbsTitleAction, HeaderActionMenu, HeaderAddButton, HeaderEditButton, PageHeader };
303
+ export { PageHeader, PageHeaderBar, PageHeaderProvider };
@@ -0,0 +1,67 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { MenuEntry } from './MenuEntries.js';
3
+ type Target = {
4
+ href: string;
5
+ external?: boolean | undefined;
6
+ /** Loads the page instead of routing, for paths the host's router doesn't serve. */
7
+ reloadDocument?: boolean | undefined;
8
+ onClick?: never;
9
+ menu?: never;
10
+ } | {
11
+ onClick: () => void;
12
+ href?: never;
13
+ external?: never;
14
+ reloadDocument?: never;
15
+ menu?: never;
16
+ } | {
17
+ menu: MenuEntry[];
18
+ href?: never;
19
+ external?: never;
20
+ reloadDocument?: never;
21
+ onClick?: never;
22
+ };
23
+ export type PageHeaderActionProps = Target & {
24
+ icon: ComponentType<{
25
+ className?: string;
26
+ }>;
27
+ /** The visible text, or with `iconOnly` the name its tooltip shows. */
28
+ label: string;
29
+ /** A fuller name for assistive technology than the visible label, such as
30
+ * "Save organization flags" for "Save". */
31
+ ariaLabel?: string | undefined;
32
+ /** The icon alone, named by its tooltip, such as an edit pencil beside the title. */
33
+ iconOnly?: boolean | undefined;
34
+ disabled?: boolean | undefined;
35
+ disabledReason?: string | undefined;
36
+ /** A hint shown on hover and focus while it can be pressed. */
37
+ tooltip?: string | undefined;
38
+ /** In the error color, as a destructive menu entry is, for an action that
39
+ * removes or breaks something. */
40
+ destructive?: boolean | undefined;
41
+ /** A toggle's state, such as a favorite: set, it reads as pressed. Buttons only. */
42
+ pressed?: boolean | undefined;
43
+ };
44
+ /**
45
+ * An action in the page header: a link (`href`, `external` for a new tab,
46
+ * `reloadDocument` for a path the router doesn't serve), a
47
+ * button (`onClick`) or a menu (`menu`). Labeled actions read in the link
48
+ * color, so the page's commands stand apart from the trail.
49
+ */
50
+ export declare function PageHeaderAction({ icon: Icon, label, ariaLabel, iconOnly, disabled, disabledReason, tooltip, destructive, pressed, ...target }: PageHeaderActionProps): import("react").JSX.Element;
51
+ export type PageHeaderAddButtonProps = Target & {
52
+ /** The full action, such as "New cluster": the tooltip and accessible name. */
53
+ label: string;
54
+ /** The visible verb. */
55
+ word?: 'add' | 'create' | undefined;
56
+ disabled?: boolean | undefined;
57
+ disabledReason?: string | undefined;
58
+ };
59
+ /** The page's "+ Add" or "+ Create": with `menu`, a chooser of what to make. */
60
+ export declare function PageHeaderAddButton({ label, word, disabled, disabledReason, ...target }: PageHeaderAddButtonProps): import("react").JSX.Element;
61
+ /** The "…" menu of actions on the page's subject. Renders nothing without items. */
62
+ export declare function PageHeaderMenu({ items, label, }: {
63
+ items: MenuEntry[];
64
+ /** Defaults to the strings' "Actions". */
65
+ label?: string | undefined;
66
+ }): import("react").JSX.Element | null;
67
+ export {};
@@ -0,0 +1,107 @@
1
+ import { cx } from "../cx.js";
2
+ import { buttonClasses } from "./buttonClasses.js";
3
+ import { AddIcon, MoreIcon } from "../icons.js";
4
+ import { Tooltip } from "./Tooltip.js";
5
+ import { useStrings } from "./Provider.js";
6
+ import { AppLink } from "./AppLink.js";
7
+ import { ActionMenu } from "./ActionMenu.js";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/components/PageHeaderAction.tsx
10
+ /**
11
+ * The one control under every header action, so links, buttons and menus share
12
+ * the look, the disabled behavior and the tooltip. A disabled control stays
13
+ * focusable (`aria-disabled`, not `disabled`), so its reason is reachable by
14
+ * keyboard and pointer, where a disabled button would swallow both.
15
+ */
16
+ function HeaderControl({ name, tooltip, disabled, disabledReason, pressed, className, children, href, external, reloadDocument, onClick, menu }) {
17
+ const { common } = useStrings();
18
+ if (menu && !disabled) return /* @__PURE__ */ jsx(ActionMenu, {
19
+ items: menu,
20
+ label: name,
21
+ triggerClassName: className,
22
+ children
23
+ });
24
+ const reason = disabled ? disabledReason : void 0;
25
+ if (href) return /* @__PURE__ */ jsx(Tooltip, {
26
+ content: disabled ? disabledReason ?? tooltip : tooltip,
27
+ children: /* @__PURE__ */ jsx(AppLink, {
28
+ href,
29
+ reloadDocument,
30
+ target: external ? "_blank" : void 0,
31
+ disabled,
32
+ "aria-label": reason ? `${name}. ${reason}` : external ? `${name} ${common.opensInNewTab}` : name,
33
+ className,
34
+ children
35
+ })
36
+ });
37
+ return /* @__PURE__ */ jsx(Tooltip, {
38
+ content: disabled ? disabledReason ?? tooltip : tooltip,
39
+ children: /* @__PURE__ */ jsx("button", {
40
+ type: "button",
41
+ onClick: disabled ? void 0 : onClick,
42
+ "aria-label": reason ? `${name}. ${reason}` : name,
43
+ "aria-disabled": disabled || void 0,
44
+ "aria-pressed": pressed,
45
+ className,
46
+ children
47
+ })
48
+ });
49
+ }
50
+ /**
51
+ * An action in the page header: a link (`href`, `external` for a new tab,
52
+ * `reloadDocument` for a path the router doesn't serve), a
53
+ * button (`onClick`) or a menu (`menu`). Labeled actions read in the link
54
+ * color, so the page's commands stand apart from the trail.
55
+ */
56
+ function PageHeaderAction({ icon: Icon, label, ariaLabel, iconOnly = false, disabled = false, disabledReason, tooltip, destructive = false, pressed, ...target }) {
57
+ const tone = destructive ? "*:text-(--theme-error-text)" : iconOnly ? void 0 : "*:text-(--theme-link)";
58
+ return /* @__PURE__ */ jsxs(HeaderControl, {
59
+ ...target,
60
+ name: ariaLabel ?? label,
61
+ tooltip: tooltip ?? label,
62
+ disabled,
63
+ disabledReason,
64
+ pressed,
65
+ className: cx(buttonClasses({
66
+ variant: "ghost",
67
+ size: "xs",
68
+ iconOnly
69
+ }), tone),
70
+ children: [/* @__PURE__ */ jsx(Icon, {}), !iconOnly && /* @__PURE__ */ jsx("span", {
71
+ className: "@max-md:sr-only",
72
+ children: label
73
+ })]
74
+ });
75
+ }
76
+ /** The page's "+ Add" or "+ Create": with `menu`, a chooser of what to make. */
77
+ function PageHeaderAddButton({ label, word = "add", disabled = false, disabledReason, ...target }) {
78
+ const { common } = useStrings();
79
+ return /* @__PURE__ */ jsxs(HeaderControl, {
80
+ ...target,
81
+ name: label,
82
+ tooltip: label,
83
+ disabled,
84
+ disabledReason,
85
+ className: buttonClasses({
86
+ variant: "outline",
87
+ size: "xs"
88
+ }),
89
+ children: [/* @__PURE__ */ jsx(AddIcon, {}), /* @__PURE__ */ jsx("span", {
90
+ className: "@max-md:sr-only",
91
+ children: word === "create" ? common.create : common.add
92
+ })]
93
+ });
94
+ }
95
+ /** The "…" menu of actions on the page's subject. Renders nothing without items. */
96
+ function PageHeaderMenu({ items, label }) {
97
+ const { common } = useStrings();
98
+ if (items.length === 0) return null;
99
+ return /* @__PURE__ */ jsx(PageHeaderAction, {
100
+ icon: MoreIcon,
101
+ iconOnly: true,
102
+ label: label ?? common.actions,
103
+ menu: items
104
+ });
105
+ }
106
+ //#endregion
107
+ export { PageHeaderAction, PageHeaderAddButton, PageHeaderMenu };
@@ -1,8 +1,6 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { ChevronLeftIcon, ChevronRightIcon } from "../icons.js";
3
- import { buttonClasses } from "./buttonClasses.js";
4
- import { Tooltip } from "./Tooltip.js";
5
- import { useLink, useStrings } from "./Provider.js";
3
+ import { useStrings } from "./Provider.js";
6
4
  import { IconButton } from "./IconButton.js";
7
5
  import { Select } from "./Select.js";
8
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -70,19 +68,11 @@ function Pagination({ page, pageSize, total, hasNext, onPageChange, pageHref, pa
70
68
  });
71
69
  }
72
70
  function PageStep({ to, enabled, label, icon, onPageChange, pageHref }) {
73
- const Link = useLink();
74
- if (pageHref && enabled) return /* @__PURE__ */ jsx(Tooltip, {
75
- content: label,
76
- children: /* @__PURE__ */ jsx(Link, {
77
- href: pageHref(to),
78
- "aria-label": label,
79
- className: buttonClasses({
80
- variant: "outline",
81
- size: "sm",
82
- iconOnly: true
83
- }),
84
- children: icon
85
- })
71
+ if (pageHref && enabled) return /* @__PURE__ */ jsx(IconButton, {
72
+ variant: "outline",
73
+ icon,
74
+ label,
75
+ href: pageHref(to)
86
76
  });
87
77
  return /* @__PURE__ */ jsx(IconButton, {
88
78
  variant: "outline",