@parallelworks/ui 0.19.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/dist/{chunks/memoryStore-CC91S5LP.js → ai/adapter/memoryStore.js} +2 -2
  2. package/dist/ai/adapter/mock/index.js +2 -80
  3. package/dist/ai/adapter/mock/mockAdapter.js +81 -0
  4. package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
  5. package/dist/ai/adapter/openai/errors.js +37 -0
  6. package/dist/ai/adapter/openai/index.js +5 -333
  7. package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
  8. package/dist/ai/adapter/openai/responses.js +108 -0
  9. package/dist/ai/adapter/openai/sse.js +112 -0
  10. package/dist/{chunks/types-Yy0NrerF.js → ai/adapter/types.js} +1 -1
  11. package/dist/ai/agent/classifier.js +12 -0
  12. package/dist/ai/agent/diff.js +223 -0
  13. package/dist/ai/agent/diffHighlight.js +101 -0
  14. package/dist/ai/agent/permissions.js +29 -0
  15. package/dist/ai/agent/toolfmt.js +383 -0
  16. package/dist/ai/agent/transcript.js +127 -0
  17. package/dist/ai/components/AllocationSelector.js +56 -0
  18. package/dist/ai/components/AttachmentManager.js +144 -0
  19. package/dist/ai/components/AttachmentUpload.js +179 -0
  20. package/dist/ai/components/BlockedGroupBanner.js +29 -0
  21. package/dist/ai/components/BranchNavigator.js +57 -0
  22. package/dist/ai/components/ChatEmptyState.js +171 -0
  23. package/dist/ai/components/ChatInput.js +265 -0
  24. package/dist/ai/components/ChatLayout.js +15 -0
  25. package/dist/ai/components/ChatMessage.js +338 -0
  26. package/dist/ai/components/ChatMessageList.js +301 -0
  27. package/dist/ai/components/ChatSidebar.js +345 -0
  28. package/dist/ai/components/ChatThread.js +198 -0
  29. package/dist/ai/components/ComposerChrome.js +50 -0
  30. package/dist/ai/components/ComposerFrame.js +33 -0
  31. package/dist/ai/components/DragOverlay.js +20 -0
  32. package/dist/ai/components/ModelSelector.js +343 -0
  33. package/dist/ai/components/ShareDialog.js +260 -0
  34. package/dist/ai/components/SlashMenu.js +91 -0
  35. package/dist/ai/components/agent/ActivityLine.js +151 -0
  36. package/dist/ai/components/agent/AgentMessageParts.js +721 -0
  37. package/dist/ai/components/agent/PermissionPicker.js +38 -0
  38. package/dist/ai/components/attachmentKeys.js +10 -0
  39. package/dist/ai/core/ChatProvider.js +848 -0
  40. package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
  41. package/dist/ai/core/chatReducer.js +223 -0
  42. package/dist/ai/core/config.js +45 -0
  43. package/dist/ai/core/events.js +4 -0
  44. package/dist/ai/core/exportConversation.js +42 -0
  45. package/dist/ai/core/greeting.js +38 -0
  46. package/dist/ai/core/parts.js +84 -0
  47. package/dist/ai/core/useDragDrop.js +36 -0
  48. package/dist/ai/core/useKeyboardShortcuts.js +77 -0
  49. package/dist/ai/core/useSSEStream.js +98 -0
  50. package/dist/ai/index.js +43 -6644
  51. package/dist/ai/strings.js +175 -0
  52. package/dist/ai/ui/Avatar.js +35 -0
  53. package/dist/ai/ui/Dropdown.js +110 -0
  54. package/dist/ai/ui/Markdown.js +79 -0
  55. package/dist/ai/ui/Notice.js +77 -0
  56. package/dist/ai/ui/codeBreaks.js +36 -0
  57. package/dist/ai/ui/prose.js +13 -0
  58. package/dist/ai/ui/sidebar.js +148 -0
  59. package/dist/ai/utils.js +23 -0
  60. package/dist/components/AddMenu.js +46 -0
  61. package/dist/components/Avatar.js +46 -0
  62. package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
  63. package/dist/components/Breadcrumbs.js +83 -0
  64. package/dist/components/Button.js +83 -0
  65. package/dist/components/Callout.js +23 -0
  66. package/dist/components/Card.js +31 -0
  67. package/dist/components/CollapsiblePanel.js +77 -0
  68. package/dist/{chunks/ConfirmModal-aOR4O1QE.js → components/ConfirmModal.js} +4 -9
  69. package/dist/components/CopyToClipboard.js +81 -0
  70. package/dist/{chunks/Markdown-CM-FenNE.js → components/CreateModal.js} +9 -116
  71. package/dist/components/DescriptionList.js +42 -0
  72. package/dist/components/Dropdown.js +349 -0
  73. package/dist/components/EmptyState.js +27 -0
  74. package/dist/components/Filter.js +32 -0
  75. package/dist/components/FilterPill.js +13 -0
  76. package/dist/components/IconButton.js +34 -0
  77. package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
  78. package/dist/components/Input.js +88 -0
  79. package/dist/components/LetterBadge.js +13 -0
  80. package/dist/components/Listbox.js +123 -0
  81. package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
  82. package/dist/components/Markdown.js +15 -0
  83. package/dist/components/MultiSelect.js +171 -0
  84. package/dist/components/Overview.js +39 -0
  85. package/dist/components/PageHeader.js +194 -0
  86. package/dist/components/Pagination.js +35 -0
  87. package/dist/{chunks/Provider-CDYurraE.js → components/Provider.js} +1 -1
  88. package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
  89. package/dist/components/SectionHeader.js +11 -0
  90. package/dist/components/SettingModeOption.js +30 -0
  91. package/dist/components/SettingRow.js +35 -0
  92. package/dist/components/SettingsCard.js +31 -0
  93. package/dist/components/SettingsGroup.js +17 -0
  94. package/dist/components/Sort.js +42 -0
  95. package/dist/components/StatusBadge.js +63 -0
  96. package/dist/components/SwitchToggle.js +59 -0
  97. package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
  98. package/dist/{chunks/Table-DU2MW8xz.js → components/Table.js} +3 -3
  99. package/dist/components/Toggle.js +20 -0
  100. package/dist/{chunks/Tooltip-K5HJr4_p.js → components/Tooltip.js} +2 -27
  101. package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
  102. package/dist/{chunks/focus-D_iZJTW1.js → components/focus.js} +1 -1
  103. package/dist/components/ghostButton.js +7 -0
  104. package/dist/components/inputClasses.js +4 -0
  105. package/dist/components/keys.js +27 -0
  106. package/dist/components/relativeTime.js +32 -0
  107. package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
  108. package/dist/{chunks/duration-BOv8Phl8.js → duration.js} +1 -1
  109. package/dist/editor/Editor.js +13 -0
  110. package/dist/editor/EditorField.js +53 -0
  111. package/dist/editor/FormCodePanel.js +173 -0
  112. package/dist/{chunks/Monaco-DJ8MW-Dq.js → editor/Monaco.js} +4 -2
  113. package/dist/editor/index.js +6 -1
  114. package/dist/editor/themes.js +91 -0
  115. package/dist/editor/workers.js +29 -0
  116. package/dist/editor/yaml.js +15 -0
  117. package/dist/file-explorer/FileExplorer.js +1402 -0
  118. package/dist/file-explorer/FileExplorerContext.js +370 -0
  119. package/dist/{chunks/CodePreviewEditor-YqQBz1YL.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
  120. package/dist/file-explorer/FilePreview/Inline.js +45 -0
  121. package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
  122. package/dist/file-explorer/FilePreview/Viewer.js +526 -0
  123. package/dist/file-explorer/FilePreview/index.js +266 -0
  124. package/dist/file-explorer/FilePreview/parse.js +63 -0
  125. package/dist/file-explorer/FilePreview/previewCache.js +98 -0
  126. package/dist/file-explorer/FilePreview/previewType.js +151 -0
  127. package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
  128. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
  129. package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
  130. package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
  131. package/dist/file-explorer/FileRow.js +165 -0
  132. package/dist/file-explorer/FileRowsWindow.js +92 -0
  133. package/dist/file-explorer/FileTable.js +172 -0
  134. package/dist/file-explorer/FileTableHeaders.js +49 -0
  135. package/dist/file-explorer/LoadMoreRow.js +46 -0
  136. package/dist/file-explorer/TreeView.js +376 -0
  137. package/dist/file-explorer/helpers.js +537 -0
  138. package/dist/file-explorer/index.js +7 -5671
  139. package/dist/file-explorer/lib/batch.js +39 -0
  140. package/dist/file-explorer/lib/errors.js +9 -0
  141. package/dist/file-explorer/lib/paging.js +124 -0
  142. package/dist/file-explorer/lib/utils.js +322 -0
  143. package/dist/file-explorer/toasts.js +400 -0
  144. package/dist/file-explorer/treeRows.js +73 -0
  145. package/dist/file-explorer/useNearEnd.js +27 -0
  146. package/dist/form/FieldWrapper.js +54 -0
  147. package/dist/form/Fields/BooleanField.js +51 -0
  148. package/dist/form/Fields/CheckboxGroupField.js +72 -0
  149. package/dist/form/Fields/ColorField.js +33 -0
  150. package/dist/form/Fields/DropdownField.js +49 -0
  151. package/dist/form/Fields/DurationField.js +130 -0
  152. package/dist/form/Fields/LabelField.js +19 -0
  153. package/dist/form/Fields/MultiDropdownField.js +40 -0
  154. package/dist/form/Fields/NumberField.js +51 -0
  155. package/dist/form/Fields/ObjectField.js +54 -0
  156. package/dist/form/Fields/RadioField.js +49 -0
  157. package/dist/form/Fields/RangeField.js +58 -0
  158. package/dist/form/Fields/ResourceOptionIcon.js +21 -0
  159. package/dist/form/Fields/StepField.js +6 -0
  160. package/dist/form/Fields/StringField.js +37 -0
  161. package/dist/form/Fields/TextAreaField.js +31 -0
  162. package/dist/form/Fields/durationValidity.js +11 -0
  163. package/dist/form/Form.js +643 -0
  164. package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
  165. package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
  166. package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
  167. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
  168. package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
  169. package/dist/form/Wizard/WizardContainer.js +138 -0
  170. package/dist/form/Wizard/WizardNavigation.js +92 -0
  171. package/dist/form/Wizard/WizardStepContent.js +35 -0
  172. package/dist/form/Wizard/WizardStepIndicator.js +108 -0
  173. package/dist/form/Wizard/useWizardState.js +118 -0
  174. package/dist/form/Wizard/utils.js +34 -0
  175. package/dist/form/fieldContext.js +26 -0
  176. package/dist/form/fieldRegistry.js +50 -0
  177. package/dist/form/index.js +17 -2321
  178. package/dist/{chunks/FieldWrapper-Cszwg-4B.js → form/lib.js} +2 -97
  179. package/dist/form/useParsedOpts.js +62 -0
  180. package/dist/form/usePrevious.js +11 -0
  181. package/dist/form/utils/checkShowIfObject.js +14 -0
  182. package/dist/form/utils/duration.js +69 -0
  183. package/dist/form/utils/getParentValue.js +23 -0
  184. package/dist/form/utils/getPlaceholder.js +9 -0
  185. package/dist/form/utils/getValueUsingPath.js +23 -0
  186. package/dist/form/utils/secondaryField.js +22 -0
  187. package/dist/graph/AnnotationBanner.js +141 -0
  188. package/dist/graph/Collapse.js +41 -0
  189. package/dist/graph/DependencyGraph.js +1398 -0
  190. package/dist/graph/JobSummary.js +104 -0
  191. package/dist/graph/MatrixGroup.js +107 -0
  192. package/dist/graph/Reveal.js +57 -0
  193. package/dist/graph/TreeView.js +711 -0
  194. package/dist/graph/index.js +1 -2519
  195. package/dist/graph/types.js +6 -0
  196. package/dist/index.js +46 -599
  197. package/dist/list/ActionToolbar.js +184 -0
  198. package/dist/list/HoverCard.js +73 -0
  199. package/dist/list/ListPage.js +145 -0
  200. package/dist/list/ListPager.js +59 -0
  201. package/dist/list/ListTable.js +163 -0
  202. package/dist/list/ListViewControls.js +372 -0
  203. package/dist/list/RowContextMenu.js +350 -0
  204. package/dist/list/UserHoverCard.js +117 -0
  205. package/dist/list/index.js +11 -2
  206. package/dist/list/listSearch.js +137 -0
  207. package/dist/list/listView.js +210 -0
  208. package/dist/list/textEntry.js +10 -0
  209. package/dist/{chunks/logviewer-BTKuQWzw.js → logviewer/LogViewer.js} +4 -82
  210. package/dist/logviewer/index.js +2 -1
  211. package/dist/logviewer/segments.js +80 -0
  212. package/package.json +1 -1
  213. package/dist/chunks/PageHeader-0kG_iypP.js +0 -692
  214. package/dist/chunks/SwitchToggle-DShNdcao.js +0 -758
  215. package/dist/chunks/editor-Dk1rbV75.js +0 -361
  216. package/dist/chunks/list-CJm875m7.js +0 -1418
  217. /package/dist/{chunks/MarkdownImpl-CyeTVvSt.js → components/MarkdownImpl.js} +0 -0
@@ -1,1418 +0,0 @@
1
- import { n as useLink, o as usePersistedState, r as useNavigation, u as useStrings } from "./Provider-CDYurraE.js";
2
- import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, DisplayIcon, ErrorIcon, FilterIcon, MenuIcon, SearchIcon, XIcon } from "../icons.js";
3
- import { n as TOOLTIP_ID, r as TooltipInfo, s as positionKeys } from "./Tooltip-K5HJr4_p.js";
4
- import { _ as useRowMenuActive, c as PageHeader, f as rowContextMenuProps, u as MoreButton, w as Avatar } from "./PageHeader-0kG_iypP.js";
5
- import { n as Table } from "./Table-DU2MW8xz.js";
6
- import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
7
- import cx from "classnames";
8
- import { Children, Fragment, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
9
- import { useLocalStorage } from "usehooks-ts";
10
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
- import { createPortal } from "react-dom";
12
- //#region src/list/ActionToolbar.tsx
13
- var OverflowMenuContext = createContext(false);
14
- /** Omits data-tooltip-content when absent — react-tooltip treats an explicit undefined as an empty tooltip anchor. */
15
- function tipProps(content, place) {
16
- return {
17
- "data-tooltip-id": TOOLTIP_ID,
18
- "data-tooltip-place": place,
19
- ...content !== void 0 && { "data-tooltip-content": content }
20
- };
21
- }
22
- function ToolbarItemView({ item }) {
23
- if ("element" in item) return /* @__PURE__ */ jsx(Fragment$1, { children: item.element });
24
- return /* @__PURE__ */ jsx(ToolbarButton, { action: item });
25
- }
26
- function ToolbarButton({ action }) {
27
- const inOverflowMenu = useContext(OverflowMenuContext);
28
- const Link = useLink();
29
- if (inOverflowMenu) return /* @__PURE__ */ jsx(OverflowRow, { item: action });
30
- const className = cx("inline-flex items-center gap-x-0.5 h-9 px-2 rounded-md text-[13.5px] uppercase font-semibold transition-colors whitespace-nowrap", "bg-(--theme-element) text-(--theme-element-text)", action.disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:bg-(--theme-element-hover)");
31
- const tooltipProps = tipProps(action.disabled ? action.disabledTooltip : action.enabledTooltip, "bottom");
32
- const Icon = action.icon;
33
- const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
34
- className: "h-6 flex items-center",
35
- children: /* @__PURE__ */ jsx(Icon, { className: cx("w-auto", action.iconClassName) })
36
- }), action.label] });
37
- if (action.href && !action.disabled) return /* @__PURE__ */ jsx(Link, {
38
- to: action.href,
39
- className,
40
- ...tooltipProps,
41
- children: content
42
- });
43
- if (action.disabled) return /* @__PURE__ */ jsx("span", {
44
- className: "inline-flex",
45
- ...tooltipProps,
46
- children: /* @__PURE__ */ jsx("button", {
47
- type: "button",
48
- disabled: true,
49
- className,
50
- children: content
51
- })
52
- });
53
- return /* @__PURE__ */ jsx("button", {
54
- type: "button",
55
- onClick: action.onClick,
56
- className,
57
- ...tooltipProps,
58
- children: content
59
- });
60
- }
61
- /** A single overflowed item rendered as a full-width row inside the `…` menu. */
62
- function OverflowRow({ item }) {
63
- const Link = useLink();
64
- const Icon = item.icon;
65
- const className = cx("flex w-full items-center gap-x-1 px-4 py-2 text-[13px] uppercase font-semibold link hover:theme-hover", item.disabled && "opacity-50 cursor-not-allowed");
66
- const tooltipProps = tipProps(item.disabled ? item.disabledTooltip : item.enabledTooltip, "left");
67
- const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
68
- className: "w-5 h-6 flex items-center",
69
- children: /* @__PURE__ */ jsx(Icon, { className: cx(item.iconClassName) })
70
- }), item.label] });
71
- if (item.href && !item.disabled) return /* @__PURE__ */ jsx(MenuItem, {
72
- as: Link,
73
- to: item.href,
74
- className,
75
- ...tooltipProps,
76
- children: content
77
- });
78
- return /* @__PURE__ */ jsx(MenuItem, {
79
- as: "button",
80
- type: "button",
81
- onClick: item.onClick,
82
- disabled: item.disabled ?? false,
83
- className,
84
- ...tooltipProps,
85
- children: content
86
- });
87
- }
88
- function OverflowMenu({ items }) {
89
- const { list: t } = useStrings();
90
- return /* @__PURE__ */ jsxs(Menu, {
91
- as: "div",
92
- className: "relative inline-block text-left",
93
- children: [/* @__PURE__ */ jsx(MenuButton, {
94
- "aria-label": t.moreActions,
95
- className: "inline-flex items-center justify-center h-9 w-9 rounded-md border theme-border link hover:theme-hover transition-colors",
96
- "data-tooltip-id": TOOLTIP_ID,
97
- "data-tooltip-content": t.moreActions,
98
- "data-testid": "action-toolbar-menu-button",
99
- children: /* @__PURE__ */ jsx(MenuIcon, {})
100
- }), /* @__PURE__ */ jsx(MenuItems, {
101
- unmount: false,
102
- anchor: {
103
- to: "bottom end",
104
- gap: 4
105
- },
106
- className: "z-50 min-w-56 rounded-md border theme-border bg-(--theme-app-bg) shadow-lg py-1 focus:outline-none",
107
- children: /* @__PURE__ */ jsx(OverflowMenuContext.Provider, {
108
- value: true,
109
- children: items.map((item) => "element" in item ? /* @__PURE__ */ jsx(Fragment, { children: item.element }, item.key) : /* @__PURE__ */ jsx(OverflowRow, { item }, item.key))
110
- })
111
- })]
112
- });
113
- }
114
- var MENU_BUTTON_WIDTH = 44;
115
- var ITEM_GAP = 4;
116
- function fitCount(widths, available, gap, menuWidth) {
117
- if (widths.length === 0) return 0;
118
- if (widths.reduce((sum, w) => sum + w, 0) + gap * (widths.length - 1) <= available) return widths.length;
119
- let used = menuWidth;
120
- let count = 0;
121
- for (const w of widths) {
122
- if (used + gap + w > available) break;
123
- used += gap + w;
124
- count++;
125
- }
126
- return count;
127
- }
128
- /** Measures in a layout effect (before paint) so the inline/overflow split never flashes. */
129
- function CollapsibleRight({ items }) {
130
- const wrapRef = useRef(null);
131
- const itemRefs = useRef({});
132
- const widthCache = useRef({});
133
- const [available, setAvailable] = useState(Number.POSITIVE_INFINITY);
134
- const [count, setCount] = useState(items.length);
135
- useLayoutEffect(() => {
136
- const el = wrapRef.current;
137
- if (!el) return;
138
- const update = () => setAvailable(el.clientWidth);
139
- update();
140
- const observer = new ResizeObserver(update);
141
- observer.observe(el);
142
- return () => observer.disconnect();
143
- }, []);
144
- useLayoutEffect(() => {
145
- for (const key in itemRefs.current) {
146
- const el = itemRefs.current[key];
147
- if (el) widthCache.current[key] = el.offsetWidth;
148
- }
149
- const next = fitCount(items.map((item) => widthCache.current[item.key] ?? 0), available, ITEM_GAP, MENU_BUTTON_WIDTH);
150
- setCount((prev) => prev === next ? prev : next);
151
- });
152
- const visible = items.slice(0, count);
153
- const overflow = items.slice(count);
154
- return /* @__PURE__ */ jsxs("div", {
155
- ref: wrapRef,
156
- className: "flex-1 min-w-0 flex items-center justify-end gap-1",
157
- children: [visible.map((item) => /* @__PURE__ */ jsx("span", {
158
- ref: (el) => {
159
- itemRefs.current[item.key] = el;
160
- },
161
- className: "inline-flex shrink-0",
162
- children: /* @__PURE__ */ jsx(ToolbarItemView, { item })
163
- }, item.key)), overflow.length > 0 && /* @__PURE__ */ jsx(OverflowMenu, { items: overflow })]
164
- });
165
- }
166
- /** Pins a detail-page toolbar just below the sticky breadcrumb strip (top-10 matches the strip's 40px height). */
167
- var stickyToolbarClasses = "sticky top-10 z-10 bg-(--theme-app-bg)";
168
- /** Pins a detail-page tabs row under the pinned toolbar (top-22 = 40px strip + 48px toolbar block). */
169
- var stickyTabsClasses = "sticky top-22 z-10 bg-(--theme-app-bg)";
170
- /** Pins a detail-page tabs row directly under the breadcrumb strip when the page has no pinned toolbar (top-10 = the strip's height). */
171
- var stickyTabsUnderHeaderClasses = "sticky top-10 z-10 bg-(--theme-app-bg)";
172
- /** Action groups left, secondary actions right (collapsing into a `…` menu). */
173
- function ActionToolbar({ groups, right = [], sticky = false }) {
174
- const visibleGroups = groups.map((group) => group.filter((a) => !a.hidden)).filter((group) => group.length > 0);
175
- const visibleRight = right.filter((a) => !a.hidden);
176
- return /* @__PURE__ */ jsxs("div", {
177
- className: cx("flex items-center gap-2.5", sticky && cx("sticky top-10 z-10 bg-(--theme-app-bg)", "py-1.5")),
178
- children: [visibleGroups.length > 0 && /* @__PURE__ */ jsx("div", {
179
- className: "flex items-center gap-2.5 shrink-0",
180
- children: visibleGroups.map((group, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-(--theme-border)" }), /* @__PURE__ */ jsx("div", {
181
- className: "flex items-center gap-1",
182
- children: group.map((item) => /* @__PURE__ */ jsx(ToolbarItemView, { item }, item.key))
183
- })] }, group[0]?.key ?? i))
184
- }), visibleRight.length > 0 && /* @__PURE__ */ jsx(CollapsibleRight, { items: visibleRight })]
185
- });
186
- }
187
- //#endregion
188
- //#region src/list/HoverCard.tsx
189
- function useHoverCard() {
190
- const triggerRef = useRef(null);
191
- const [coords, setCoords] = useState(null);
192
- const openTimer = useRef(void 0);
193
- const closeTimer = useRef(void 0);
194
- useEffect(() => () => {
195
- clearTimeout(openTimer.current);
196
- clearTimeout(closeTimer.current);
197
- }, []);
198
- const scheduleOpen = () => {
199
- clearTimeout(closeTimer.current);
200
- openTimer.current = setTimeout(() => {
201
- const rect = triggerRef.current?.getBoundingClientRect();
202
- if (rect) setCoords({
203
- x: rect.left,
204
- y: rect.bottom
205
- });
206
- }, 350);
207
- };
208
- const scheduleClose = () => {
209
- clearTimeout(openTimer.current);
210
- closeTimer.current = setTimeout(() => setCoords(null), 150);
211
- };
212
- const keepOpen = () => clearTimeout(closeTimer.current);
213
- return {
214
- triggerRef,
215
- coords,
216
- scheduleOpen,
217
- scheduleClose,
218
- keepOpen
219
- };
220
- }
221
- function HoverCardSurface({ x, y, widthClassName, padded = true, relayoutKey, onKeepOpen, onLeave, children }) {
222
- const ref = useRef(null);
223
- const [pos, setPos] = useState(null);
224
- useLayoutEffect(() => {
225
- if (!ref.current) return;
226
- const { width, height } = ref.current.getBoundingClientRect();
227
- const pad = 8;
228
- const left = Math.max(pad, Math.min(x, window.innerWidth - width - pad));
229
- const top = y + 8 + height + pad > window.innerHeight ? Math.max(pad, y - 8 - height) : y + 8;
230
- setPos({
231
- left,
232
- top
233
- });
234
- }, [
235
- x,
236
- y,
237
- relayoutKey
238
- ]);
239
- return createPortal(/* @__PURE__ */ jsx("div", {
240
- ref,
241
- role: "none",
242
- onMouseEnter: onKeepOpen,
243
- onMouseLeave: onLeave,
244
- onFocus: onKeepOpen,
245
- onBlur: onLeave,
246
- className: cx("fixed z-[9999] overflow-hidden rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) shadow-xl backdrop-blur-xl", padded && "p-3", widthClassName),
247
- style: {
248
- left: pos?.left ?? x,
249
- top: pos?.top ?? y + 8,
250
- visibility: pos ? "visible" : "hidden"
251
- },
252
- children
253
- }), document.querySelector(".ds-root") ?? document.body);
254
- }
255
- //#endregion
256
- //#region src/list/ListTable.tsx
257
- var NAME_TEXT = "font-medium text-[13px] leading-tight text-(--theme-app) truncate";
258
- var SUBTITLE_TEXT = "text-xs leading-tight text-(--theme-muted-text-color) truncate";
259
- /** table-auto + horizontal scroll; the name column has a min-width floor so it can't be crushed. */
260
- var listTableProps = {
261
- borderless: true,
262
- panel: false,
263
- rounded: false,
264
- theadClassName: "text-(--theme-muted-text-color)",
265
- tbodyClassName: "divide-y border-b border-(--theme-border) divide-(--theme-border)",
266
- className: "table-auto w-full [&_td:first-child]:pl-4 [&_th:first-child]:pl-4 [&_td:last-child]:pr-4 [&_th:last-child]:pr-4"
267
- };
268
- /** Widths come only from the header row, so a windowed body cannot shift the
269
- * columns as different content scrolls into view. */
270
- var listTableFixedProps = {
271
- ...listTableProps,
272
- className: listTableProps.className.replace("table-auto", "table-fixed")
273
- };
274
- function NameCell({ icon, name, subtitle, href, badge, title }) {
275
- const Link = useLink();
276
- const inner = /* @__PURE__ */ jsxs(Fragment$1, { children: [icon, /* @__PURE__ */ jsxs("div", {
277
- className: "min-w-0",
278
- children: [/* @__PURE__ */ jsxs("div", {
279
- className: "flex min-w-0 items-center gap-1.5",
280
- children: [/* @__PURE__ */ jsx("div", {
281
- className: cx(NAME_TEXT, href && "group-hover/namecell:text-(--theme-link)"),
282
- title,
283
- children: name
284
- }), badge]
285
- }), subtitle && /* @__PURE__ */ jsx("div", {
286
- className: SUBTITLE_TEXT,
287
- children: subtitle
288
- })]
289
- })] });
290
- return /* @__PURE__ */ jsx(Table.Item, {
291
- className: "py-2.5",
292
- children: href ? /* @__PURE__ */ jsx(Link, {
293
- to: href,
294
- className: "group/namecell flex items-center gap-2.5 min-w-0 cursor-pointer",
295
- children: inner
296
- }) : /* @__PURE__ */ jsx("div", {
297
- className: "flex items-center gap-2.5 min-w-0",
298
- children: inner
299
- })
300
- });
301
- }
302
- function GroupRow({ label, count, colSpan }) {
303
- return /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
304
- colSpan,
305
- className: "bg-(--theme-muted-panel-bg) py-1.5 text-xs font-medium",
306
- children: [
307
- /* @__PURE__ */ jsx("span", {
308
- className: "text-(--theme-app)",
309
- children: label
310
- }),
311
- /* @__PURE__ */ jsx("span", {
312
- className: "mx-1.5 opacity-50",
313
- children: "·"
314
- }),
315
- /* @__PURE__ */ jsx("span", {
316
- className: "text-(--theme-muted-text-color)",
317
- children: count
318
- })
319
- ]
320
- }) });
321
- }
322
- function ListError({ error, noun = "data" }) {
323
- const { list: t } = useStrings();
324
- const message = error && typeof error === "object" && "message" in error ? String(error.message) : void 0;
325
- return /* @__PURE__ */ jsxs("div", {
326
- className: "flex flex-col items-center justify-center gap-3 py-20 text-center",
327
- children: [/* @__PURE__ */ jsx(ErrorIcon, { className: "h-10 w-10 text-red-400 opacity-80" }), /* @__PURE__ */ jsxs("div", {
328
- className: "space-y-1",
329
- children: [/* @__PURE__ */ jsx("p", {
330
- className: "text-sm font-medium text-(--theme-app)",
331
- children: t.couldNotLoad(noun)
332
- }), message && /* @__PURE__ */ jsx("p", {
333
- className: "max-w-md text-xs text-(--theme-muted-text-color)",
334
- children: message
335
- })]
336
- })]
337
- });
338
- }
339
- function ListSkeleton({ rows = 6 }) {
340
- const { loading } = useStrings();
341
- return /* @__PURE__ */ jsxs("div", {
342
- role: "status",
343
- "aria-label": loading,
344
- className: "divide-y border-b border-(--theme-border) divide-(--theme-border)",
345
- children: [positionKeys(rows, "skeleton").map((key) => /* @__PURE__ */ jsxs("div", {
346
- "aria-hidden": "true",
347
- className: "flex items-center gap-3 px-4 py-2.5 animate-pulse",
348
- children: [
349
- /* @__PURE__ */ jsx("div", { className: "h-5 w-5 rounded bg-(--theme-muted-panel-bg)" }),
350
- /* @__PURE__ */ jsxs("div", {
351
- className: "space-y-1.5",
352
- children: [/* @__PURE__ */ jsx("div", { className: "h-3 w-40 rounded bg-(--theme-muted-panel-bg)" }), /* @__PURE__ */ jsx("div", { className: "h-2.5 w-24 rounded bg-(--theme-muted-panel-bg)" })]
353
- }),
354
- /* @__PURE__ */ jsx("div", { className: "ml-auto h-3 w-20 rounded bg-(--theme-muted-panel-bg)" })
355
- ]
356
- }, key)), /* @__PURE__ */ jsx("span", {
357
- className: "sr-only",
358
- children: loading
359
- })]
360
- });
361
- }
362
- /** Prevents the name column from being crushed under table-auto layout during horizontal overflow. */
363
- var NAME_MIN_WIDTH_PX = 200;
364
- /** Trailing cell geometry: icon-button size + gap + padding, sized to fit exactly its pinned buttons. */
365
- var ACTION_BUTTON_PX = 24;
366
- var ACTION_GAP_PX = 2;
367
- var ACTIONS_CELL_PAD_PX = 28;
368
- /** Width the trailing actions column reserves for `count` always-visible
369
- * buttons plus the hover `⋯` trigger. */
370
- function actionsColumnWidth(count) {
371
- const buttons = count + 1;
372
- return buttons * ACTION_BUTTON_PX + (buttons - 1) * ACTION_GAP_PX + ACTIONS_CELL_PAD_PX;
373
- }
374
- /** Shared leading selection checkbox for multi-select rows: stops the row-click
375
- * and suppresses shift-click text selection. Wrap in <Table.Item> for `leading`. */
376
- function RowSelectCheckbox({ checked, label, onToggle }) {
377
- return /* @__PURE__ */ jsx("input", {
378
- type: "checkbox",
379
- "aria-label": label,
380
- checked,
381
- onChange: () => {},
382
- onClick: (e) => {
383
- e.stopPropagation();
384
- onToggle(e);
385
- },
386
- onMouseDown: (e) => {
387
- if (e.shiftKey) e.preventDefault();
388
- },
389
- className: "h-4 w-4 shrink-0 rounded border-(--theme-border) text-blue-500 focus:ring-blue-500 focus:ring-offset-0 cursor-pointer"
390
- });
391
- }
392
- /** Sizes to fit pinned buttons + ⋯; below ~44rem only ⋯ remains. Pass a view's
393
- * chrome (structurally: showActions + actionsWidth) or actionCount. */
394
- function ListActionsHeader({ view, actionCount = 0, className }) {
395
- if (view && !view.showActions) return null;
396
- const width = view ? view.actionsWidth : actionsColumnWidth(actionCount);
397
- return /* @__PURE__ */ jsx(Table.Header, {
398
- caps: false,
399
- style: {
400
- width,
401
- minWidth: width
402
- },
403
- className: cx("relative py-2 text-right @max-[43rem]:w-[52px]! @max-[43rem]:min-w-[52px]!", className),
404
- children: /* @__PURE__ */ jsx("span", {
405
- className: "sr-only",
406
- children: "Actions"
407
- })
408
- });
409
- }
410
- ListActionsHeader.isTableHeader = true;
411
- //#endregion
412
- //#region src/list/ListPage.tsx
413
- /** Negative offsets (admin/org) cancel the shell's pt-4 so rows don't peek through the gap above the pinned header. */
414
- var SECTION_STICKY_TOP = {
415
- main: "top-0",
416
- kubernetes: "top-0",
417
- admin: "-top-4",
418
- org: "-top-4"
419
- };
420
- /** Whether the shell adds pt-4 padding that the edge-to-edge list must cancel with a negative margin. */
421
- var SECTION_SHELL_PADDED = {
422
- main: false,
423
- kubernetes: false,
424
- admin: true,
425
- org: true
426
- };
427
- /** Defaults mean rows outside a ListView fully show actions (no view = show everything). */
428
- var RowActionsContext = createContext({
429
- show: true,
430
- always: false,
431
- columnClasses: []
432
- });
433
- function useRowActions(view) {
434
- return useMemo(() => ({
435
- show: view?.showActions ?? true,
436
- always: view?.alwaysShowActions ?? false,
437
- columnClasses: view?.columnClasses ?? []
438
- }), [
439
- view?.showActions,
440
- view?.alwaysShowActions,
441
- view?.columnClasses
442
- ]);
443
- }
444
- function ListRowActionsProvider({ view, children }) {
445
- const rowActions = useRowActions(view);
446
- return /* @__PURE__ */ jsx(RowActionsContext.Provider, {
447
- value: rowActions,
448
- children
449
- });
450
- }
451
- function ListPage({ section = "main", shellPadded, embedded = false, breadcrumbs, title, titleAction, actions, toolbar, view, children }) {
452
- const cancelShellPadding = shellPadded ?? SECTION_SHELL_PADDED[section];
453
- return /* @__PURE__ */ jsxs("div", {
454
- className: cx("flex h-full min-h-0 flex-col", cancelShellPadding && "-m-4"),
455
- children: [embedded ? toolbar && /* @__PURE__ */ jsx("div", {
456
- className: "px-4 py-1.5",
457
- children: toolbar
458
- }) : /* @__PURE__ */ jsx(PageHeader, {
459
- stickyTopClassName: SECTION_STICKY_TOP[section],
460
- breadcrumbs,
461
- title,
462
- titleAction,
463
- actions,
464
- toolbar
465
- }), /* @__PURE__ */ jsx("div", {
466
- className: "relative @container min-h-0 flex-1 overflow-auto",
467
- children: /* @__PURE__ */ jsx(ListRowActionsProvider, {
468
- view,
469
- children
470
- })
471
- })]
472
- });
473
- }
474
- function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children }) {
475
- const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
476
- const { show: showActions, always: alwaysShowActions, columnClasses } = useContext(RowActionsContext);
477
- const styledCells = Children.toArray(children).map((child, i) => {
478
- const cls = columnClasses[i];
479
- if (!cls || !isValidElement(child)) return child;
480
- const el = child;
481
- return cloneElement(el, { className: cx(el.props.className, cls) });
482
- });
483
- const handleRowClick = (e) => {
484
- if (e.target.closest("a,button,input,label")) return;
485
- if (selectionActive && onRowSelect) {
486
- onRowSelect(e);
487
- return;
488
- }
489
- if (href) goTo(href);
490
- else onActivate?.();
491
- };
492
- const pinnedLabels = new Set((pinnedActions ?? []).map((a) => a.label));
493
- const hasOverflowItems = items.some((item) => item.kind !== "divider" && (item.kind === "submenu" || !pinnedLabels.has(item.label)));
494
- return /* @__PURE__ */ jsxs("tr", {
495
- className: cx("group", selected ? "bg-blue-500/5" : menuActive ? "theme-hover" : "hover:theme-hover", (href || onActivate || selectionActive) && "cursor-pointer", className),
496
- onClick: handleRowClick,
497
- ...rowContextMenuProps(openRowMenu, items),
498
- children: [
499
- leading,
500
- styledCells,
501
- showActions && /* @__PURE__ */ jsx(Table.Item, {
502
- className: "relative py-2.5",
503
- children: /* @__PURE__ */ jsxs("div", {
504
- className: "absolute inset-y-0 right-0 flex items-center gap-0.5 pr-4",
505
- children: [pinnedActions && pinnedActions.length > 0 && /* @__PURE__ */ jsx("span", {
506
- className: "hidden items-center gap-0.5 @[44rem]:flex",
507
- children: pinnedActions.map((action) => /* @__PURE__ */ jsx("button", {
508
- type: "button",
509
- "aria-label": action.label,
510
- "data-tooltip-id": "global-tooltip",
511
- "data-tooltip-content": action.disabled && action.tooltip ? action.tooltip : action.label,
512
- "data-tooltip-place": "bottom",
513
- "aria-disabled": action.disabled,
514
- onClick: (e) => {
515
- e.stopPropagation();
516
- if (action.disabled) return;
517
- action.onSelect();
518
- },
519
- className: cx("rounded p-1 cursor-pointer text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) aria-disabled:cursor-not-allowed aria-disabled:opacity-20 aria-disabled:hover:bg-transparent aria-disabled:hover:text-(--theme-muted-text-color) [&>svg]:h-4 [&>svg]:w-4", alwaysShowActions || menuActive ? "opacity-100" : "opacity-0 group-hover:opacity-100 focus-visible:opacity-100"),
520
- children: action.icon
521
- }, action.key))
522
- }), /* @__PURE__ */ jsx(MoreButton, {
523
- active: menuActive || alwaysShowActions,
524
- onOpen: (x, y) => openRowMenu(x, y, items),
525
- className: hasOverflowItems ? void 0 : "@[44rem]:hidden"
526
- })]
527
- })
528
- })
529
- ]
530
- });
531
- }
532
- /** Name column min-width is by role, not position, so a leading checkbox doesn't shift it. */
533
- function ListColumns({ view }) {
534
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [view.visibleColumns.map((col, i) => /* @__PURE__ */ jsx(Table.Header, {
535
- caps: false,
536
- className: cx(col.headerClassName, view.columnClasses[i]),
537
- style: col.alwaysVisible ? { minWidth: 200 } : void 0,
538
- children: col.label
539
- }, col.key)), /* @__PURE__ */ jsx(ListActionsHeader, { view })] });
540
- }
541
- ListColumns.isTableHeader = true;
542
- /** Stable `goTo` for whole-row navigation (`rowNavProps`, `ListRow`). */
543
- function useListNavigate() {
544
- return useNavigation().goTo;
545
- }
546
- //#endregion
547
- //#region src/list/textEntry.ts
548
- /** Guards list-page shortcuts from firing inside inputs, terminals, editors, and dialogs. */
549
- function isTextEntryContext(target) {
550
- const el = target;
551
- if (!el) return false;
552
- if (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable) return true;
553
- return Boolean(el.closest?.(".xterm, .monaco-editor, .cm-editor, [role=\"dialog\"]"));
554
- }
555
- //#endregion
556
- //#region src/list/ListViewControls.tsx
557
- /** Shared chrome for the toolbar control buttons (Filter, Display, Search) so
558
- * they read as a consistent button cluster — matched by ListSearchControl. */
559
- var listControlButtonClasses = "relative flex h-7 w-7 items-center justify-center rounded-md border border-(--theme-border) text-(--theme-muted-text-color) hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) transition-colors";
560
- var FLYOUT = "min-w-44 rounded-lg border border-(--theme-border) bg-(--theme-app-bg) py-1 shadow-lg";
561
- /** Toggle pill shared by the Display menu's column + pinned-action pickers. */
562
- function pillClass(on) {
563
- return cx("whitespace-nowrap rounded-full border px-2.5 py-1 text-[12px] transition-colors", on ? "border-transparent bg-(--theme-muted-panel-bg) text-(--theme-app)" : "border-(--theme-border) text-(--theme-muted-text-color) hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app)");
564
- }
565
- var SECTION_LABEL = "mb-2 text-[11px] font-medium uppercase tracking-wide text-(--theme-muted-text-color)";
566
- /** Portals into .ds-root so the panel escapes the toolbar's overflow clipping. */
567
- function AnchoredPopover({ icon, label, active, shortcut, panelClassName, children }) {
568
- const btnRef = useRef(null);
569
- const panelRef = useRef(null);
570
- const [pos, setPos] = useState(null);
571
- const close = useCallback(() => setPos(null), []);
572
- const open = useCallback(() => {
573
- const r = btnRef.current?.getBoundingClientRect();
574
- if (r) setPos({
575
- right: Math.max(8, Math.round(window.innerWidth - r.right)),
576
- top: Math.round(r.bottom) + 4
577
- });
578
- }, []);
579
- useEffect(() => {
580
- if (!pos) return;
581
- const onKey = (e) => {
582
- if (e.key === "Escape") close();
583
- };
584
- const onScroll = (e) => {
585
- if (e.target instanceof Node && panelRef.current?.contains(e.target)) return;
586
- close();
587
- };
588
- window.addEventListener("keydown", onKey);
589
- window.addEventListener("scroll", onScroll, true);
590
- window.addEventListener("resize", close);
591
- return () => {
592
- window.removeEventListener("keydown", onKey);
593
- window.removeEventListener("scroll", onScroll, true);
594
- window.removeEventListener("resize", close);
595
- };
596
- }, [pos, close]);
597
- useEffect(() => {
598
- if (!shortcut) return;
599
- const onKey = (e) => {
600
- if (e.metaKey || e.ctrlKey || e.altKey) return;
601
- if (e.key.toLowerCase() !== shortcut.toLowerCase()) return;
602
- if (isTextEntryContext(e.target)) return;
603
- e.preventDefault();
604
- open();
605
- };
606
- window.addEventListener("keydown", onKey);
607
- return () => window.removeEventListener("keydown", onKey);
608
- }, [shortcut, open]);
609
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("button", {
610
- ref: btnRef,
611
- type: "button",
612
- onClick: () => pos ? close() : open(),
613
- "aria-label": label,
614
- "data-tooltip-id": TOOLTIP_ID,
615
- "data-tooltip-content": label,
616
- "data-tooltip-shortcut": shortcut ? shortcut.toUpperCase() : void 0,
617
- className: cx(listControlButtonClasses, active && "border-(--theme-link) text-(--theme-app)"),
618
- children: [icon, active && /* @__PURE__ */ jsx("span", { className: "absolute right-0 top-0 h-1.5 w-1.5 rounded-full bg-(--theme-link)" })]
619
- }), pos && createPortal(/* @__PURE__ */ jsx("div", {
620
- role: "none",
621
- className: "fixed inset-0 z-50",
622
- onMouseDown: close,
623
- onContextMenu: (e) => {
624
- e.preventDefault();
625
- close();
626
- },
627
- children: /* @__PURE__ */ jsx("div", {
628
- ref: panelRef,
629
- role: "menu",
630
- onMouseDown: (e) => e.stopPropagation(),
631
- className: cx("absolute rounded-lg border border-(--theme-border) bg-(--theme-app-bg) shadow-lg", panelClassName),
632
- style: {
633
- right: pos.right,
634
- top: pos.top
635
- },
636
- children
637
- })
638
- }), document.querySelector(".ds-root") ?? document.body)] });
639
- }
640
- /** Small on/off switch for the Display menu's boolean options. */
641
- function Switch$1({ checked, onChange, label }) {
642
- return /* @__PURE__ */ jsx("button", {
643
- type: "button",
644
- role: "switch",
645
- "aria-checked": checked,
646
- "aria-label": label,
647
- onClick: onChange,
648
- className: cx("relative inline-flex h-4 w-7 shrink-0 items-center rounded-full transition-colors", checked ? "bg-(--theme-link)" : "bg-(--theme-border)"),
649
- children: /* @__PURE__ */ jsx("span", { className: cx("inline-block h-3 w-3 rounded-full bg-white transition-transform", checked ? "translate-x-3.5" : "translate-x-0.5") })
650
- });
651
- }
652
- function ListDisplayMenu({ view, label }) {
653
- const t = useStrings().list;
654
- const toggleable = view.columns.filter((c) => !c.alwaysVisible);
655
- const hasGrouping = view.groupBys.length > 0;
656
- const orderDirLabel = view.orderDir === "asc" ? t.ascending : t.descending;
657
- return /* @__PURE__ */ jsxs(AnchoredPopover, {
658
- icon: /* @__PURE__ */ jsx(DisplayIcon, { className: "h-3.5 w-3.5" }),
659
- label: label ?? t.displayOptions,
660
- panelClassName: "w-72 p-3",
661
- children: [
662
- hasGrouping && /* @__PURE__ */ jsxs("div", {
663
- className: "flex items-center justify-between gap-3",
664
- children: [/* @__PURE__ */ jsx("span", {
665
- className: "text-[13px] text-(--theme-app)",
666
- children: t.grouping
667
- }), /* @__PURE__ */ jsx(GroupingDropdown, { view })]
668
- }),
669
- view.orderBys.length > 0 && /* @__PURE__ */ jsxs("div", {
670
- className: cx("flex items-center justify-between gap-3", hasGrouping && "mt-2.5"),
671
- children: [/* @__PURE__ */ jsx("span", {
672
- className: "text-[13px] text-(--theme-app)",
673
- children: t.ordering
674
- }), /* @__PURE__ */ jsxs("div", {
675
- className: "flex items-center gap-1",
676
- children: [/* @__PURE__ */ jsx("button", {
677
- type: "button",
678
- onClick: view.toggleOrderDir,
679
- "aria-label": orderDirLabel,
680
- title: orderDirLabel,
681
- className: "flex h-7 w-7 items-center justify-center rounded-md border border-(--theme-border) text-(--theme-muted-text-color) hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app)",
682
- children: view.orderDir === "asc" ? /* @__PURE__ */ jsx(ChevronUpIcon, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(ChevronDownIcon, { className: "h-3 w-3" })
683
- }), /* @__PURE__ */ jsx(OrderingDropdown, { view })]
684
- })]
685
- }),
686
- /* @__PURE__ */ jsxs("div", {
687
- className: cx("flex items-center justify-between gap-3", (hasGrouping || view.orderBys.length > 0) && "mt-2.5"),
688
- children: [/* @__PURE__ */ jsx("span", {
689
- className: "text-[13px] text-(--theme-app)",
690
- children: t.columnHeaders
691
- }), /* @__PURE__ */ jsx(Switch$1, {
692
- checked: view.showColumnHeaders,
693
- onChange: view.toggleColumnHeaders,
694
- label: t.showColumnHeaders
695
- })]
696
- }),
697
- /* @__PURE__ */ jsxs("div", {
698
- className: "mt-2.5 flex items-center justify-between gap-3",
699
- children: [/* @__PURE__ */ jsx("span", {
700
- className: "text-[13px] text-(--theme-app)",
701
- children: t.showActions
702
- }), /* @__PURE__ */ jsx(Switch$1, {
703
- checked: view.showActions,
704
- onChange: view.toggleShowActions,
705
- label: t.showRowActions
706
- })]
707
- }),
708
- view.showActions && /* @__PURE__ */ jsxs("div", {
709
- className: "mt-2.5 flex items-center justify-between gap-3 pl-3",
710
- children: [/* @__PURE__ */ jsx("span", {
711
- className: "text-[13px] text-(--theme-muted-text-color)",
712
- children: t.alwaysShowActions
713
- }), /* @__PURE__ */ jsx(Switch$1, {
714
- checked: view.alwaysShowActions,
715
- onChange: view.toggleAlwaysShowActions,
716
- label: t.alwaysShowRowActions
717
- })]
718
- }),
719
- toggleable.length > 0 && /* @__PURE__ */ jsxs("div", {
720
- className: "mt-3 border-t border-(--theme-border) pt-3",
721
- children: [/* @__PURE__ */ jsx("div", {
722
- className: SECTION_LABEL,
723
- children: t.displayProperties
724
- }), /* @__PURE__ */ jsx("div", {
725
- className: "flex flex-wrap gap-1.5",
726
- children: toggleable.map((c) => /* @__PURE__ */ jsx("button", {
727
- type: "button",
728
- onClick: () => view.toggleColumn(c.key),
729
- className: pillClass(view.isVisible(c.key)),
730
- children: c.label
731
- }, c.key))
732
- })]
733
- }),
734
- view.showActions && view.actions.length > 0 && /* @__PURE__ */ jsxs("div", {
735
- className: "mt-3 border-t border-(--theme-border) pt-3",
736
- children: [/* @__PURE__ */ jsxs("div", {
737
- className: cx(SECTION_LABEL, "flex items-center gap-1"),
738
- children: [t.pinnedActions, /* @__PURE__ */ jsx(TooltipInfo, {
739
- className: "inline-flex",
740
- text: t.pinnedActionsHelp
741
- })]
742
- }), /* @__PURE__ */ jsx("div", {
743
- className: "flex flex-wrap gap-1.5",
744
- children: view.actions.map((a) => /* @__PURE__ */ jsx("button", {
745
- type: "button",
746
- onClick: () => view.togglePinned(a.key),
747
- className: pillClass(view.isPinned(a.key)),
748
- children: a.label
749
- }, a.key))
750
- })]
751
- }),
752
- !view.isDisplayDefault && /* @__PURE__ */ jsx("div", {
753
- className: "mt-3 border-t border-(--theme-border) pt-2",
754
- children: /* @__PURE__ */ jsx("button", {
755
- type: "button",
756
- onClick: view.resetDisplay,
757
- className: "link text-[12px]",
758
- children: t.reset
759
- })
760
- })
761
- ]
762
- });
763
- }
764
- /** Capture phase: the popover panel stops mousedown propagation, so a
765
- * bubble-phase listener never sees clicks inside it. */
766
- function useCloseOnOutsideClick(open, close) {
767
- const ref = useRef(null);
768
- useEffect(() => {
769
- if (!open) return;
770
- const onDown = (e) => {
771
- if (e.target instanceof Node && !ref.current?.contains(e.target)) close();
772
- };
773
- document.addEventListener("mousedown", onDown, true);
774
- return () => document.removeEventListener("mousedown", onDown, true);
775
- }, [open, close]);
776
- return ref;
777
- }
778
- function GroupingDropdown({ view }) {
779
- const t = useStrings().list;
780
- const [open, setOpen] = useState(false);
781
- const ref = useCloseOnOutsideClick(open, useCallback(() => setOpen(false), []));
782
- const current = view.groupBys.find((g) => g.value === view.groupBy);
783
- return /* @__PURE__ */ jsxs("div", {
784
- ref,
785
- className: "relative",
786
- children: [/* @__PURE__ */ jsxs("button", {
787
- type: "button",
788
- onClick: () => setOpen((o) => !o),
789
- className: "flex items-center gap-1.5 rounded-md border border-(--theme-border) px-2 py-1 text-[12px] text-(--theme-app) hover:bg-(--theme-muted-panel-bg)",
790
- children: [current?.label ?? t.none, /* @__PURE__ */ jsx(ChevronDownIcon, { className: "h-2.5 w-2.5 opacity-60" })]
791
- }), open && /* @__PURE__ */ jsx("div", {
792
- className: cx("absolute right-0 top-full z-20 mt-1", FLYOUT),
793
- children: view.groupBys.map((g) => /* @__PURE__ */ jsxs("button", {
794
- type: "button",
795
- onClick: () => {
796
- view.setGroupBy(g.value);
797
- setOpen(false);
798
- },
799
- className: cx("flex w-full items-center justify-between gap-3 px-3 py-1.5 text-left text-[13px] hover:bg-(--theme-muted-panel-bg)", view.groupBy === g.value ? "text-(--theme-app)" : "text-(--theme-muted-text-color)"),
800
- children: [g.label, view.groupBy === g.value && /* @__PURE__ */ jsx(CheckIcon, { className: "h-3 w-3" })]
801
- }, g.value))
802
- })]
803
- });
804
- }
805
- function OrderingDropdown({ view }) {
806
- const t = useStrings().list;
807
- const [open, setOpen] = useState(false);
808
- const ref = useCloseOnOutsideClick(open, useCallback(() => setOpen(false), []));
809
- const current = view.orderBys.find((o) => o.value === view.orderBy);
810
- return /* @__PURE__ */ jsxs("div", {
811
- ref,
812
- className: "relative",
813
- children: [/* @__PURE__ */ jsxs("button", {
814
- type: "button",
815
- onClick: () => setOpen((o) => !o),
816
- className: "flex items-center gap-1.5 rounded-md border border-(--theme-border) px-2 py-1 text-[12px] text-(--theme-app) hover:bg-(--theme-muted-panel-bg)",
817
- children: [current?.label ?? t.none, /* @__PURE__ */ jsx(ChevronDownIcon, { className: "h-2.5 w-2.5 opacity-60" })]
818
- }), open && /* @__PURE__ */ jsx("div", {
819
- className: cx("absolute right-0 top-full z-20 mt-1", FLYOUT),
820
- children: view.orderBys.map((o) => /* @__PURE__ */ jsxs("button", {
821
- type: "button",
822
- onClick: () => {
823
- view.setOrderBy(o.value);
824
- setOpen(false);
825
- },
826
- className: cx("flex w-full items-center justify-between gap-3 px-3 py-1.5 text-left text-[13px] hover:bg-(--theme-muted-panel-bg)", view.orderBy === o.value ? "text-(--theme-app)" : "text-(--theme-muted-text-color)"),
827
- children: [o.label, view.orderBy === o.value && /* @__PURE__ */ jsx(CheckIcon, { className: "h-3 w-3" })]
828
- }, o.value))
829
- })]
830
- });
831
- }
832
- function ListFilterMenu({ view, label }) {
833
- const t = useStrings().list;
834
- if (view.facets.length === 0) return null;
835
- return /* @__PURE__ */ jsx(AnchoredPopover, {
836
- icon: /* @__PURE__ */ jsx(FilterIcon, { className: "h-3.5 w-3.5" }),
837
- label: label ?? t.filter,
838
- active: view.activeFilterCount > 0,
839
- shortcut: "f",
840
- panelClassName: "w-56 py-1",
841
- children: /* @__PURE__ */ jsx(FilterMenuBody, { view })
842
- });
843
- }
844
- /** Single-open submenu prevents overlapping; resets on panel remount so Filter opens clean. */
845
- function FilterMenuBody({ view }) {
846
- const t = useStrings().list;
847
- const [openKey, setOpenKey] = useState(null);
848
- const closeTimer = useRef(void 0);
849
- const openFacet = (key) => {
850
- clearTimeout(closeTimer.current);
851
- setOpenKey(key);
852
- };
853
- const scheduleClose = () => {
854
- closeTimer.current = setTimeout(() => setOpenKey(null), 160);
855
- };
856
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
857
- className: "flex items-center justify-between px-3 py-1",
858
- children: [/* @__PURE__ */ jsx("span", {
859
- className: "text-[11px] font-medium uppercase tracking-wide text-(--theme-muted-text-color)",
860
- children: t.filter
861
- }), view.activeFilterCount > 0 && /* @__PURE__ */ jsx("button", {
862
- type: "button",
863
- onClick: view.clearFilters,
864
- className: "text-[12px] text-(--theme-muted-text-color) hover:text-(--theme-app)",
865
- children: t.clear
866
- })]
867
- }), view.facets.map((facet) => /* @__PURE__ */ jsx(FilterFacetRow, {
868
- view,
869
- facet,
870
- open: openKey === facet.key,
871
- onEnter: () => openFacet(facet.key),
872
- onLeave: scheduleClose
873
- }, facet.key))] });
874
- }
875
- function FilterFacetRow({ view, facet, open, onEnter, onLeave }) {
876
- const selected = view.filters[facet.key] ?? [];
877
- return /* @__PURE__ */ jsxs("div", {
878
- className: "relative",
879
- role: "none",
880
- onMouseEnter: onEnter,
881
- onMouseLeave: onLeave,
882
- onFocus: onEnter,
883
- onBlur: onLeave,
884
- children: [/* @__PURE__ */ jsxs("button", {
885
- type: "button",
886
- "aria-expanded": open,
887
- "aria-haspopup": "menu",
888
- onClick: onEnter,
889
- className: "flex w-full cursor-default items-center justify-between gap-3 px-3 py-1.5 text-[13px] text-(--theme-app) hover:bg-(--theme-muted-panel-bg)",
890
- children: [/* @__PURE__ */ jsx("span", { children: facet.label }), /* @__PURE__ */ jsxs("span", {
891
- className: "flex items-center gap-1.5",
892
- children: [selected.length > 0 && /* @__PURE__ */ jsx("span", {
893
- className: "text-[11px] text-(--theme-muted-text-color)",
894
- children: selected.length
895
- }), /* @__PURE__ */ jsx(ChevronRightIcon, { className: "h-3 w-3 opacity-60" })]
896
- })]
897
- }), open && /* @__PURE__ */ jsx("div", {
898
- className: cx("absolute right-full top-0 z-10 max-h-[70vh] overflow-y-auto", FLYOUT),
899
- children: facet.options.map((option) => {
900
- const checked = selected.includes(option.value);
901
- return /* @__PURE__ */ jsxs("button", {
902
- type: "button",
903
- onClick: () => view.setFacetValues(facet.key, checked ? selected.filter((v) => v !== option.value) : [...selected, option.value]),
904
- className: "flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] text-(--theme-app) hover:bg-(--theme-muted-panel-bg)",
905
- children: [
906
- /* @__PURE__ */ jsx("span", {
907
- className: "flex h-3.5 w-3.5 shrink-0 items-center justify-center text-(--theme-link)",
908
- children: checked && /* @__PURE__ */ jsx(CheckIcon, { className: "h-3 w-3" })
909
- }),
910
- option.icon,
911
- option.label
912
- ]
913
- }, option.value);
914
- })
915
- })]
916
- });
917
- }
918
- //#endregion
919
- //#region src/list/ListPager.tsx
920
- /** Page sizes offered by the pager's per-page select; first entry is the default. */
921
- var LIST_PAGE_SIZES = [
922
- 50,
923
- 100,
924
- 200
925
- ];
926
- /** Footer pager bar for paginated new-nav lists: "{start}–{end} of {total}" with prev/next. Renders nothing for a single page. */
927
- function ListPager({ page, pageSize, total, onPageChange, pageSizes = LIST_PAGE_SIZES, onPageSizeChange, hasNext }) {
928
- const t = useStrings().list;
929
- const pageCount = Math.max(1, Math.ceil(total / pageSize));
930
- const start = page * pageSize + 1;
931
- const end = Math.min(total, (page + 1) * pageSize);
932
- const smallest = Math.min(...pageSizes);
933
- const showSizeSelect = !!onPageSizeChange && (total > smallest || pageSize > smallest);
934
- if (pageCount <= 1 && !showSizeSelect) return null;
935
- return /* @__PURE__ */ jsxs("div", {
936
- className: "flex items-center justify-between px-2.5 h-10 shrink-0 border-t theme-border",
937
- children: [/* @__PURE__ */ jsxs("div", {
938
- className: "flex items-center gap-3 text-xs text-(--theme-muted-text-color)",
939
- children: [/* @__PURE__ */ jsx("span", { children: t.pager(start, end, total) }), showSizeSelect && /* @__PURE__ */ jsxs("label", {
940
- className: "flex items-center gap-1",
941
- children: [t.perPage, /* @__PURE__ */ jsx("select", {
942
- value: pageSize,
943
- onChange: (e) => onPageSizeChange(Number(e.target.value)),
944
- className: "theme-input rounded px-1.5 py-0.5 text-xs",
945
- children: pageSizes.map((size) => /* @__PURE__ */ jsx("option", {
946
- value: size,
947
- children: size
948
- }, size))
949
- })]
950
- })]
951
- }), /* @__PURE__ */ jsxs("div", {
952
- className: "flex items-center gap-1",
953
- children: [/* @__PURE__ */ jsx("button", {
954
- type: "button",
955
- "aria-label": t.paginationPrevious,
956
- disabled: page === 0,
957
- onClick: () => onPageChange(page - 1),
958
- className: cx(listControlButtonClasses, "disabled:opacity-40 disabled:pointer-events-none"),
959
- children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "w-3.5 h-3.5" })
960
- }), /* @__PURE__ */ jsx("button", {
961
- type: "button",
962
- "aria-label": t.paginationNext,
963
- disabled: hasNext === void 0 ? page >= pageCount - 1 : !hasNext,
964
- onClick: () => onPageChange(page + 1),
965
- className: cx(listControlButtonClasses, "disabled:opacity-40 disabled:pointer-events-none"),
966
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3.5 h-3.5" })
967
- })]
968
- })]
969
- });
970
- }
971
- //#endregion
972
- //#region src/list/listSearch.tsx
973
- var NO_TERMS = [];
974
- /** Pass `enabled: false` to suspend the global ⌘F/`/` hijack (e.g. when search isn't shown and browser Find should stay active). */
975
- function useListSearch(enabled = true, storageKey) {
976
- const [filter, setFilter] = useState("");
977
- const [searchOpen, setSearchOpen] = useState(false);
978
- const [storedTerms, setStoredTerms] = useLocalStorage(`${storageKey ?? "list"}:searchTerms`, NO_TERMS);
979
- const terms = storageKey ? storedTerms : NO_TERMS;
980
- const inputRef = useRef(null);
981
- useEffect(() => {
982
- if (searchOpen) inputRef.current?.focus();
983
- }, [searchOpen]);
984
- useEffect(() => {
985
- if (!enabled) return;
986
- const onKey = (e) => {
987
- if (e.key === "Escape" && searchOpen) {
988
- setSearchOpen(false);
989
- setFilter("");
990
- return;
991
- }
992
- if (isTextEntryContext(e.target)) return;
993
- if ((e.key === "f" || e.key === "F") && (e.metaKey || e.ctrlKey)) {
994
- e.preventDefault();
995
- setSearchOpen(true);
996
- } else if (e.key === "/") {
997
- e.preventDefault();
998
- setSearchOpen(true);
999
- }
1000
- };
1001
- window.addEventListener("keydown", onKey);
1002
- return () => window.removeEventListener("keydown", onKey);
1003
- }, [searchOpen, enabled]);
1004
- const openSearch = useCallback(() => setSearchOpen(true), []);
1005
- const closeSearch = useCallback(() => {
1006
- setSearchOpen(false);
1007
- setFilter("");
1008
- }, []);
1009
- const addTerm = useCallback((term) => {
1010
- const trimmed = term.trim();
1011
- if (!storageKey || !trimmed) return;
1012
- setStoredTerms((prev) => prev.some((t) => t.toLowerCase() === trimmed.toLowerCase()) ? prev : [...prev, trimmed]);
1013
- setFilter("");
1014
- }, [storageKey, setStoredTerms]);
1015
- const removeTerm = useCallback((term) => setStoredTerms((prev) => prev.filter((t) => t !== term)), [setStoredTerms]);
1016
- const clearSearch = useCallback(() => {
1017
- if (storageKey) setStoredTerms(NO_TERMS);
1018
- setSearchOpen(false);
1019
- setFilter("");
1020
- }, [storageKey, setStoredTerms]);
1021
- const trimmedFilter = filter.trim();
1022
- return {
1023
- filter,
1024
- setFilter,
1025
- searchOpen,
1026
- openSearch,
1027
- closeSearch,
1028
- clearSearch,
1029
- terms,
1030
- addTerm,
1031
- removeTerm,
1032
- queries: trimmedFilter ? [...terms, trimmedFilter] : terms,
1033
- inputRef
1034
- };
1035
- }
1036
- function ListSearchControl({ search, placeholder, label = "Search" }) {
1037
- if (search.searchOpen || search.terms.length > 0) return /* @__PURE__ */ jsxs("div", {
1038
- className: "flex min-h-7 w-72 max-w-[55vw] flex-wrap items-center gap-1.5 rounded-md border border-(--theme-border) bg-(--theme-muted-panel-bg) px-2 py-0.5",
1039
- children: [
1040
- /* @__PURE__ */ jsx(SearchIcon, { className: "w-3.5 h-3.5 shrink-0 text-(--theme-muted-text-color)" }),
1041
- search.terms.map((term) => /* @__PURE__ */ jsxs("span", {
1042
- className: "flex max-w-40 items-center gap-1 rounded border border-(--theme-border) bg-(--theme-app-bg) px-1.5 py-px text-[12px] text-(--theme-app)",
1043
- children: [/* @__PURE__ */ jsx("span", {
1044
- className: "truncate",
1045
- children: term
1046
- }), /* @__PURE__ */ jsx("button", {
1047
- type: "button",
1048
- onClick: () => search.removeTerm(term),
1049
- "aria-label": `Remove filter ${term}`,
1050
- className: "shrink-0 rounded p-0.5 text-(--theme-muted-text-color) hover:text-(--theme-app) transition-colors",
1051
- children: /* @__PURE__ */ jsx(XIcon, { className: "w-2.5 h-2.5" })
1052
- })]
1053
- }, term)),
1054
- /* @__PURE__ */ jsx("input", {
1055
- ref: search.inputRef,
1056
- value: search.filter,
1057
- onChange: (e) => search.setFilter(e.target.value),
1058
- onFocus: search.openSearch,
1059
- onKeyDown: (e) => {
1060
- if (e.key === "Enter") {
1061
- e.preventDefault();
1062
- search.addTerm(search.filter);
1063
- } else if (e.key === "Backspace" && !search.filter) {
1064
- const lastTerm = search.terms.at(-1);
1065
- if (lastTerm) search.removeTerm(lastTerm);
1066
- }
1067
- },
1068
- placeholder,
1069
- className: "min-w-24 flex-1 bg-transparent text-[13px] text-(--theme-app) placeholder:text-(--theme-muted-text-color) focus:outline-none"
1070
- }),
1071
- /* @__PURE__ */ jsx("button", {
1072
- type: "button",
1073
- onClick: search.clearSearch,
1074
- "aria-label": "Clear search",
1075
- className: "shrink-0 rounded p-0.5 text-(--theme-muted-text-color) hover:text-(--theme-app) transition-colors",
1076
- children: /* @__PURE__ */ jsx(XIcon, { className: "w-3 h-3" })
1077
- })
1078
- ]
1079
- });
1080
- return /* @__PURE__ */ jsx("button", {
1081
- type: "button",
1082
- onClick: search.openSearch,
1083
- "aria-label": label,
1084
- "data-tooltip-id": TOOLTIP_ID,
1085
- "data-tooltip-content": label,
1086
- "data-tooltip-shortcut": "⌘+F",
1087
- className: listControlButtonClasses,
1088
- children: /* @__PURE__ */ jsx(SearchIcon, { className: "w-3.5 h-3.5" })
1089
- });
1090
- }
1091
- function ListCount({ total, matches, filterActive, noun, nounPlural }) {
1092
- return /* @__PURE__ */ jsx("div", {
1093
- className: "flex items-center h-9",
1094
- children: /* @__PURE__ */ jsx("span", {
1095
- className: "text-[13px] text-(--theme-muted-text-color)",
1096
- children: filterActive ? `${matches} of ${total} ${nounPlural}` : `${total} ${total === 1 ? noun : nounPlural}`
1097
- })
1098
- });
1099
- }
1100
- //#endregion
1101
- //#region src/list/listView.tsx
1102
- var PRIORITY_SHOW_CLASS = {
1103
- low: "hidden @[52rem]:table-cell",
1104
- medium: "hidden @[36rem]:table-cell"
1105
- };
1106
- function columnResponsiveClass(col) {
1107
- return col.priority ? PRIORITY_SHOW_CLASS[col.priority] : "";
1108
- }
1109
- var DEFAULT_ORDER = "__default";
1110
- var FAVORITE_ORDER = "__favorite";
1111
- /** Persisted grouping + column-visibility + field-filter state for a list page,
1112
- * driving the shared Display/Filter menus. */
1113
- function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], defaultGroupBy, orderBys = [], defaultOrderBy, facets = [], actions = [], actionCount, defaultShowColumnHeaders = false, defaultShowActions = true, isFavorite, isOn, createdAt }) {
1114
- const t = useStrings().list;
1115
- const tiebreak = orderBys[0]?.compare;
1116
- const effectiveOrderBys = useMemo(() => {
1117
- if (!isFavorite) return orderBys;
1118
- const favoriteRank = (row) => isFavorite(row) ? 0 : 1;
1119
- const onRank = (row) => isOn?.(row) ? 0 : 1;
1120
- const createdMs = (row) => {
1121
- const v = createdAt?.(row);
1122
- return v === null || v === void 0 || Number.isNaN(v) ? 0 : v;
1123
- };
1124
- const newestFirst = (a, b) => createdMs(b) - createdMs(a);
1125
- return [...[{
1126
- value: DEFAULT_ORDER,
1127
- label: t.orderDefault,
1128
- compare: (a, b) => onRank(a) - onRank(b) || favoriteRank(a) - favoriteRank(b) || newestFirst(a, b) || (tiebreak?.(a, b) ?? 0)
1129
- }, {
1130
- value: FAVORITE_ORDER,
1131
- label: t.orderFavorites,
1132
- compare: (a, b) => favoriteRank(a) - favoriteRank(b) || (tiebreak?.(a, b) ?? 0)
1133
- }], ...orderBys];
1134
- }, [
1135
- orderBys,
1136
- isFavorite,
1137
- isOn,
1138
- createdAt,
1139
- tiebreak,
1140
- t
1141
- ]);
1142
- const defaultGroupByValue = defaultGroupBy ?? groupBys[0]?.value ?? "";
1143
- const defaultOrderByValue = isFavorite ? DEFAULT_ORDER : defaultOrderBy ?? orderBys[0]?.value ?? "";
1144
- const defaultHiddenKeys = useMemo(() => columns.filter((c) => c.defaultHidden && !c.alwaysVisible).map((c) => c.key), [columns]);
1145
- const defaultPinnedKeys = useMemo(() => actions.filter((a) => a.defaultPinned).map((a) => a.key), [actions]);
1146
- const [hiddenKeys, setHiddenKeys] = usePersistedState(`${storageKey}:hiddenColumns`, defaultHiddenKeys);
1147
- const [pinnedKeys, setPinnedKeys] = usePersistedState(`${storageKey}:pinnedActions`, defaultPinnedKeys);
1148
- const [groupBy, setGroupBy] = usePersistedState(`${storageKey}:groupBy`, defaultGroupByValue);
1149
- const [showColumnHeaders, setShowColumnHeaders] = usePersistedState(`${storageKey}:showColumnHeaders`, defaultShowColumnHeaders);
1150
- const [showActions, setShowActions] = usePersistedState(`${storageKey}:showActions`, defaultShowActions);
1151
- const [alwaysShowActions, setAlwaysShowActions] = usePersistedState(`${storageKey}:alwaysShowActions`, false);
1152
- const [orderBy, setOrderBy] = usePersistedState(`${storageKey}:orderBy`, defaultOrderByValue);
1153
- const [orderDir, setOrderDir] = usePersistedState(`${storageKey}:orderDir`, "asc");
1154
- const [localFilters, setLocalFilters] = usePersistedState(`${storageKey}:filters`, {});
1155
- const [sharedFilters, setSharedFilters] = usePersistedState(`${sharedFiltersKey ?? storageKey}:sharedFilters`, {});
1156
- const sharedKeys = useMemo(() => new Set(facets.filter((f) => f.shared).map((f) => f.key)), [facets]);
1157
- const filters = useMemo(() => {
1158
- if (sharedKeys.size === 0) return localFilters;
1159
- const merged = {};
1160
- for (const [key, values] of Object.entries(localFilters)) if (!sharedKeys.has(key)) merged[key] = values;
1161
- for (const key of sharedKeys) {
1162
- const values = sharedFilters[key];
1163
- if (values?.length) merged[key] = values;
1164
- }
1165
- return merged;
1166
- }, [
1167
- localFilters,
1168
- sharedFilters,
1169
- sharedKeys
1170
- ]);
1171
- const pinnedCount = actionCount ?? pinnedKeys.filter((key) => actions.some((action) => action.key === key)).length;
1172
- const effectiveShowActions = showActions || alwaysShowActions;
1173
- const actionsWidth = effectiveShowActions ? actionsColumnWidth(pinnedCount) : 0;
1174
- const isVisible = useCallback((key) => {
1175
- const col = columns.find((c) => c.key === key);
1176
- return Boolean(col?.alwaysVisible) || !hiddenKeys.includes(key);
1177
- }, [columns, hiddenKeys]);
1178
- const visibleColumns = useMemo(() => columns.filter((c) => !c.inline && (c.alwaysVisible || !hiddenKeys.includes(c.key))), [columns, hiddenKeys]);
1179
- const columnClasses = useMemo(() => visibleColumns.map(columnResponsiveClass), [visibleColumns]);
1180
- const toggleColumn = useCallback((key) => setHiddenKeys((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]), [setHiddenKeys]);
1181
- const setFacetValues = useCallback((key, values) => {
1182
- const update = (prev) => {
1183
- const next = { ...prev };
1184
- if (values.length) next[key] = values;
1185
- else delete next[key];
1186
- return next;
1187
- };
1188
- if (sharedKeys.has(key)) setSharedFilters(update);
1189
- else setLocalFilters(update);
1190
- }, [
1191
- setLocalFilters,
1192
- setSharedFilters,
1193
- sharedKeys
1194
- ]);
1195
- const clearFilters = useCallback(() => {
1196
- setLocalFilters({});
1197
- if (sharedKeys.size > 0) setSharedFilters((prev) => {
1198
- const next = { ...prev };
1199
- for (const key of sharedKeys) delete next[key];
1200
- return next;
1201
- });
1202
- }, [
1203
- setLocalFilters,
1204
- setSharedFilters,
1205
- sharedKeys
1206
- ]);
1207
- const isPinned = useCallback((key) => pinnedKeys.includes(key), [pinnedKeys]);
1208
- const togglePinned = useCallback((key) => setPinnedKeys((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]), [setPinnedKeys]);
1209
- const toggleColumnHeaders = useCallback(() => setShowColumnHeaders((prev) => !prev), [setShowColumnHeaders]);
1210
- const toggleShowActions = useCallback(() => {
1211
- if (effectiveShowActions) {
1212
- setShowActions(false);
1213
- setAlwaysShowActions(false);
1214
- } else setShowActions(true);
1215
- }, [
1216
- effectiveShowActions,
1217
- setShowActions,
1218
- setAlwaysShowActions
1219
- ]);
1220
- const toggleAlwaysShowActions = useCallback(() => {
1221
- const next = !alwaysShowActions;
1222
- setAlwaysShowActions(next);
1223
- if (next) setShowActions(true);
1224
- }, [
1225
- alwaysShowActions,
1226
- setAlwaysShowActions,
1227
- setShowActions
1228
- ]);
1229
- const toggleOrderDir = useCallback(() => setOrderDir((d) => d === "asc" ? "desc" : "asc"), [setOrderDir]);
1230
- const applyOrder = useCallback((rows) => {
1231
- const opt = effectiveOrderBys.find((o) => o.value === orderBy);
1232
- if (!opt) return rows;
1233
- const sign = orderDir === "asc" ? 1 : -1;
1234
- return [...rows].sort((a, b) => opt.compare(a, b) * sign);
1235
- }, [
1236
- effectiveOrderBys,
1237
- orderBy,
1238
- orderDir
1239
- ]);
1240
- const sameKeys = (a, b) => a.length === b.length && a.every((k) => b.includes(k));
1241
- const isDisplayDefault = groupBy === defaultGroupByValue && orderBy === defaultOrderByValue && orderDir === "asc" && showColumnHeaders === defaultShowColumnHeaders && showActions === defaultShowActions && alwaysShowActions === false && sameKeys(hiddenKeys, defaultHiddenKeys) && sameKeys(pinnedKeys, defaultPinnedKeys);
1242
- const resetDisplay = useCallback(() => {
1243
- setGroupBy(defaultGroupByValue);
1244
- setOrderBy(defaultOrderByValue);
1245
- setOrderDir("asc");
1246
- setShowColumnHeaders(defaultShowColumnHeaders);
1247
- setShowActions(defaultShowActions);
1248
- setAlwaysShowActions(false);
1249
- setHiddenKeys(defaultHiddenKeys);
1250
- setPinnedKeys(defaultPinnedKeys);
1251
- }, [
1252
- setGroupBy,
1253
- setOrderBy,
1254
- setOrderDir,
1255
- defaultOrderByValue,
1256
- setShowColumnHeaders,
1257
- setShowActions,
1258
- setAlwaysShowActions,
1259
- setHiddenKeys,
1260
- setPinnedKeys,
1261
- defaultGroupByValue,
1262
- defaultHiddenKeys,
1263
- defaultPinnedKeys,
1264
- defaultShowColumnHeaders,
1265
- defaultShowActions
1266
- ]);
1267
- return {
1268
- columns,
1269
- visibleColumns,
1270
- columnClasses,
1271
- isVisible,
1272
- pinnedCount,
1273
- actionsWidth,
1274
- toggleColumn,
1275
- groupBys,
1276
- groupBy,
1277
- setGroupBy,
1278
- orderBys: effectiveOrderBys,
1279
- orderBy,
1280
- setOrderBy,
1281
- orderDir,
1282
- toggleOrderDir,
1283
- applyOrder,
1284
- facets,
1285
- filters,
1286
- setFacetValues,
1287
- clearFilters,
1288
- activeFilterCount: useMemo(() => Object.values(filters).filter((v) => v && v.length > 0).length, [filters]),
1289
- applyFilters: useCallback((rows) => rows.filter((row) => facets.every((f) => {
1290
- const selected = filters[f.key];
1291
- return !selected || selected.length === 0 ? true : f.matches(row, selected);
1292
- })), [facets, filters]),
1293
- actions,
1294
- isPinned,
1295
- togglePinned,
1296
- showColumnHeaders,
1297
- toggleColumnHeaders,
1298
- showActions: effectiveShowActions,
1299
- toggleShowActions,
1300
- alwaysShowActions,
1301
- toggleAlwaysShowActions,
1302
- isDisplayDefault,
1303
- resetDisplay
1304
- };
1305
- }
1306
- //#endregion
1307
- //#region src/list/UserHoverCard.tsx
1308
- /** Opens `card` after a short hover or on focus of `children`. */
1309
- function HoverCardTrigger({ className, children, card }) {
1310
- const { triggerRef, coords, scheduleOpen, scheduleClose, keepOpen } = useHoverCard();
1311
- return /* @__PURE__ */ jsxs("span", {
1312
- ref: triggerRef,
1313
- role: "none",
1314
- className,
1315
- onMouseEnter: scheduleOpen,
1316
- onMouseLeave: scheduleClose,
1317
- onFocus: scheduleOpen,
1318
- onBlur: scheduleClose,
1319
- children: [children, coords && card({
1320
- x: coords.x,
1321
- y: coords.y,
1322
- onKeepOpen: keepOpen,
1323
- onLeave: scheduleClose
1324
- })]
1325
- });
1326
- }
1327
- function UserHoverCard({ x, y, onKeepOpen, onLeave, username, name, avatarSrc, badge, href, linkComponent, loading = false, relayoutKey, children }) {
1328
- const strings = useStrings();
1329
- const SlotLink = useLink();
1330
- const Link = linkComponent ?? SlotLink;
1331
- const displayName = name || username;
1332
- const header = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Avatar, {
1333
- src: avatarSrc,
1334
- name: displayName,
1335
- size: "lg",
1336
- className: "shrink-0"
1337
- }), /* @__PURE__ */ jsxs("div", {
1338
- className: "min-w-0",
1339
- children: [/* @__PURE__ */ jsxs("div", {
1340
- className: "flex items-center gap-1.5",
1341
- children: [/* @__PURE__ */ jsx("span", {
1342
- className: "truncate text-sm font-semibold text-(--theme-app)",
1343
- children: displayName
1344
- }), badge && /* @__PURE__ */ jsx("span", {
1345
- className: "shrink-0 rounded-full bg-(--theme-muted-panel-bg) px-1.5 py-0.5 text-[10px] font-medium leading-none text-(--theme-muted-text-color)",
1346
- children: badge
1347
- })]
1348
- }), /* @__PURE__ */ jsx("div", {
1349
- className: "truncate text-xs text-(--theme-muted-text-color)",
1350
- children: username
1351
- })]
1352
- })] });
1353
- return /* @__PURE__ */ jsxs(HoverCardSurface, {
1354
- x,
1355
- y,
1356
- widthClassName: "w-64",
1357
- relayoutKey,
1358
- onKeepOpen,
1359
- onLeave,
1360
- children: [href ? /* @__PURE__ */ jsx(Link, {
1361
- to: href,
1362
- className: "flex items-center gap-3 transition-opacity hover:opacity-80",
1363
- children: header
1364
- }) : /* @__PURE__ */ jsx("div", {
1365
- className: "flex items-center gap-3",
1366
- children: header
1367
- }), loading ? /* @__PURE__ */ jsxs("div", {
1368
- role: "status",
1369
- "aria-label": strings.loading,
1370
- className: "mt-3 space-y-2 border-t border-(--theme-border) pt-3",
1371
- children: [
1372
- /* @__PURE__ */ jsx("div", {
1373
- "aria-hidden": "true",
1374
- className: "h-3 w-24 animate-pulse rounded theme-muted-panel"
1375
- }),
1376
- /* @__PURE__ */ jsx("div", {
1377
- "aria-hidden": "true",
1378
- className: "h-3 w-36 animate-pulse rounded theme-muted-panel"
1379
- }),
1380
- /* @__PURE__ */ jsx("span", {
1381
- className: "sr-only",
1382
- children: strings.loading
1383
- })
1384
- ]
1385
- }) : children ? /* @__PURE__ */ jsx("div", {
1386
- className: "mt-3 space-y-2 border-t border-(--theme-border) pt-3 text-[13px]",
1387
- children
1388
- }) : null]
1389
- });
1390
- }
1391
- var rowClasses = "flex gap-2 text-(--theme-muted-text-color)";
1392
- /** One detail line of a hover card: a leading icon and its content. With
1393
- * `onClick` the line is a button. */
1394
- function HoverCardRow({ icon, children, title, align = "center", truncate = false, onClick }) {
1395
- const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1396
- "aria-hidden": "true",
1397
- className: cx("flex shrink-0 items-center [&>svg]:h-3.5 [&>svg]:w-3.5", align === "start" && "mt-0.5"),
1398
- children: icon
1399
- }), /* @__PURE__ */ jsx("span", {
1400
- className: cx("min-w-0", truncate && "truncate"),
1401
- children
1402
- })] });
1403
- const alignClass = align === "start" ? "items-start" : "items-center";
1404
- if (onClick) return /* @__PURE__ */ jsx("button", {
1405
- type: "button",
1406
- title,
1407
- onClick,
1408
- className: cx(rowClasses, alignClass, "-mx-1 max-w-full rounded px-1 py-0.5 text-left transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app)"),
1409
- children: content
1410
- });
1411
- return /* @__PURE__ */ jsx("div", {
1412
- title,
1413
- className: cx(rowClasses, alignClass),
1414
- children: content
1415
- });
1416
- }
1417
- //#endregion
1418
- export { listTableProps as A, ListError as C, RowSelectCheckbox as D, NameCell as E, stickyTabsClasses as F, stickyTabsUnderHeaderClasses as I, stickyToolbarClasses as L, useHoverCard as M, ActionToolbar as N, actionsColumnWidth as O, ToolbarButton as P, ListActionsHeader as S, NAME_MIN_WIDTH_PX as T, ListPage as _, FAVORITE_ORDER as a, useListNavigate as b, ListSearchControl as c, ListPager as d, ListDisplayMenu as f, ListColumns as g, isTextEntryContext as h, DEFAULT_ORDER as i, HoverCardSurface as j, listTableFixedProps as k, useListSearch as l, listControlButtonClasses as m, HoverCardTrigger as n, useListView as o, ListFilterMenu as p, UserHoverCard as r, ListCount as s, HoverCardRow as t, LIST_PAGE_SIZES as u, ListRow as v, ListSkeleton as w, GroupRow as x, ListRowActionsProvider as y };