@parallelworks/ui 0.77.0 → 0.79.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 (268) hide show
  1. package/README.md +42 -6
  2. package/dist/access/AccessDrawer.d.ts +49 -7
  3. package/dist/access/AccessDrawer.js +243 -38
  4. package/dist/access/AccessList.d.ts +19 -1
  5. package/dist/access/AccessList.js +220 -9
  6. package/dist/access/GrantView.d.ts +3 -1
  7. package/dist/access/GrantView.js +17 -4
  8. package/dist/access/PrincipalPicker.js +5 -5
  9. package/dist/access/focus.js +1 -1
  10. package/dist/access/index.d.ts +2 -2
  11. package/dist/access/index.js +2 -2
  12. package/dist/access/strings.d.ts +25 -1
  13. package/dist/access/strings.js +27 -3
  14. package/dist/access/types.d.ts +26 -0
  15. package/dist/ai/components/AllocationSelector.js +1 -1
  16. package/dist/ai/components/AttachmentManager.js +5 -5
  17. package/dist/ai/components/AttachmentUpload.js +2 -2
  18. package/dist/ai/components/BranchNavigator.js +1 -1
  19. package/dist/ai/components/ChatActivity.js +1 -1
  20. package/dist/ai/components/ChatEmptyState.js +3 -3
  21. package/dist/ai/components/ChatInput.js +3 -5
  22. package/dist/ai/components/ChatLayout.js +1 -1
  23. package/dist/ai/components/ChatMessage.js +1 -1
  24. package/dist/ai/components/ChatMessageList.js +23 -8
  25. package/dist/ai/components/ChatSidebar.js +3 -3
  26. package/dist/ai/components/ChatThread.js +4 -4
  27. package/dist/ai/components/ComposerAddMenu.js +9 -20
  28. package/dist/ai/components/ModelSelector.js +3 -3
  29. package/dist/ai/components/PastedText.js +5 -5
  30. package/dist/ai/components/SessionView.js +1 -1
  31. package/dist/ai/components/ShareDialog.js +3 -3
  32. package/dist/ai/components/SlashMenu.d.ts +31 -4
  33. package/dist/ai/components/SlashMenu.js +36 -63
  34. package/dist/ai/components/agent/ActivityLine.js +1 -1
  35. package/dist/ai/components/agent/AgentMessageParts.js +13 -13
  36. package/dist/ai/components/conversationActions.js +3 -3
  37. package/dist/ai/core/ChatProvider.js +1 -1
  38. package/dist/ai/core/config.js +1 -1
  39. package/dist/ai/ui/ConversationSidebar.js +3 -3
  40. package/dist/ai/ui/sidebar.js +3 -3
  41. package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
  42. package/dist/components/ActionMenu.d.ts +38 -9
  43. package/dist/components/ActionMenu.js +48 -22
  44. package/dist/components/Avatar.d.ts +66 -6
  45. package/dist/components/Avatar.js +130 -18
  46. package/dist/components/Breadcrumbs.d.ts +4 -0
  47. package/dist/components/Breadcrumbs.js +26 -10
  48. package/dist/components/Button.d.ts +3 -2
  49. package/dist/components/Button.js +11 -1
  50. package/dist/components/Callout.d.ts +3 -2
  51. package/dist/components/Callout.js +3 -2
  52. package/dist/components/Card.d.ts +33 -9
  53. package/dist/components/Card.js +24 -8
  54. package/dist/components/Checkbox.d.ts +3 -1
  55. package/dist/components/Checkbox.js +4 -2
  56. package/dist/components/ColorInput.js +1 -1
  57. package/dist/components/Combobox.d.ts +23 -0
  58. package/dist/components/Combobox.js +252 -86
  59. package/dist/components/ConfirmModal.d.ts +28 -2
  60. package/dist/components/ConfirmModal.js +100 -15
  61. package/dist/components/ContextMenu.d.ts +29 -2
  62. package/dist/components/ContextMenu.js +60 -8
  63. package/dist/components/CopyButton.d.ts +21 -8
  64. package/dist/components/CopyButton.js +29 -9
  65. package/dist/components/CreateModal.d.ts +20 -4
  66. package/dist/components/CreateModal.js +28 -17
  67. package/dist/components/DatePicker.d.ts +31 -1
  68. package/dist/components/DatePicker.js +178 -47
  69. package/dist/components/DescriptionList.d.ts +13 -3
  70. package/dist/components/DescriptionList.js +43 -11
  71. package/dist/components/Disclosure.d.ts +10 -2
  72. package/dist/components/Disclosure.js +18 -7
  73. package/dist/components/Drawer.js +5 -5
  74. package/dist/components/EmptyState.d.ts +12 -4
  75. package/dist/components/EmptyState.js +18 -10
  76. package/dist/components/ErrorPage.d.ts +40 -0
  77. package/dist/components/ErrorPage.js +101 -0
  78. package/dist/components/ExternalLink.d.ts +4 -1
  79. package/dist/components/ExternalLink.js +4 -5
  80. package/dist/components/Field.d.ts +21 -6
  81. package/dist/components/Field.js +32 -16
  82. package/dist/components/FileButton.d.ts +19 -0
  83. package/dist/components/FileButton.js +32 -0
  84. package/dist/components/HoverCard.d.ts +26 -6
  85. package/dist/components/HoverCard.js +11 -5
  86. package/dist/components/IconButton.js +4 -2
  87. package/dist/components/InfoTip.js +3 -3
  88. package/dist/components/Input.d.ts +33 -3
  89. package/dist/components/Input.js +149 -14
  90. package/dist/components/Kbd.d.ts +40 -0
  91. package/dist/components/Kbd.js +168 -0
  92. package/dist/components/Listbox.d.ts +103 -0
  93. package/dist/components/Listbox.js +190 -0
  94. package/dist/components/Loader.d.ts +8 -2
  95. package/dist/components/Loader.js +16 -1
  96. package/dist/components/Markdown.js +1 -1
  97. package/dist/components/MarkdownImpl.js +1 -1
  98. package/dist/components/Menu.d.ts +20 -2
  99. package/dist/components/Menu.js +48 -19
  100. package/dist/components/MenuEntries.d.ts +41 -4
  101. package/dist/components/MenuEntries.js +266 -39
  102. package/dist/components/Meter.d.ts +50 -11
  103. package/dist/components/Meter.js +71 -21
  104. package/dist/components/Modal.d.ts +9 -3
  105. package/dist/components/Modal.js +68 -29
  106. package/dist/components/OneTimeCodeInput.js +1 -1
  107. package/dist/components/PageHeader.js +111 -7
  108. package/dist/components/PageHeaderAction.d.ts +8 -3
  109. package/dist/components/PageHeaderAction.js +122 -34
  110. package/dist/components/Pagination.d.ts +13 -1
  111. package/dist/components/Pagination.js +90 -19
  112. package/dist/components/Popover.d.ts +16 -2
  113. package/dist/components/Popover.js +20 -21
  114. package/dist/components/Provider.d.ts +172 -5
  115. package/dist/components/Provider.js +157 -14
  116. package/dist/components/RefreshButton.d.ts +13 -6
  117. package/dist/components/RefreshButton.js +17 -9
  118. package/dist/components/RunStatusIcon.d.ts +16 -7
  119. package/dist/components/RunStatusIcon.js +21 -8
  120. package/dist/components/Section.d.ts +11 -4
  121. package/dist/components/Section.js +33 -22
  122. package/dist/components/SegmentedControl.d.ts +16 -3
  123. package/dist/components/SegmentedControl.js +7 -3
  124. package/dist/components/Select.d.ts +19 -3
  125. package/dist/components/Select.js +114 -68
  126. package/dist/components/SelectionCopy.d.ts +10 -0
  127. package/dist/components/SelectionCopy.js +105 -0
  128. package/dist/components/SettingModeOption.js +1 -1
  129. package/dist/components/SettingsCard.js +1 -1
  130. package/dist/components/Skeleton.js +1 -1
  131. package/dist/components/StatusBadge.d.ts +55 -10
  132. package/dist/components/StatusBadge.js +91 -15
  133. package/dist/components/StickyBar.js +1 -1
  134. package/dist/components/Table.d.ts +22 -4
  135. package/dist/components/Table.js +46 -24
  136. package/dist/components/Tabs.d.ts +9 -4
  137. package/dist/components/Tabs.js +9 -4
  138. package/dist/components/Tag.d.ts +49 -0
  139. package/dist/components/Tag.js +199 -0
  140. package/dist/components/ThemeEditor.js +1 -1
  141. package/dist/components/Toast.js +1 -1
  142. package/dist/components/ToggleGroup.d.ts +4 -1
  143. package/dist/components/ToggleGroup.js +30 -13
  144. package/dist/components/Tooltip.d.ts +9 -3
  145. package/dist/components/Tooltip.js +55 -39
  146. package/dist/components/buttonClasses.d.ts +5 -2
  147. package/dist/components/buttonClasses.js +11 -10
  148. package/dist/components/chart.d.ts +69 -0
  149. package/dist/components/chart.js +145 -0
  150. package/dist/components/comboboxItems.d.ts +7 -1
  151. package/dist/components/comboboxItems.js +6 -2
  152. package/dist/components/dismiss.d.ts +5 -1
  153. package/dist/components/dismiss.js +6 -2
  154. package/dist/components/floating.d.ts +4 -4
  155. package/dist/components/floating.js +5 -5
  156. package/dist/components/headerActionsFit.d.ts +37 -0
  157. package/dist/components/headerActionsFit.js +81 -0
  158. package/dist/components/pageHeaderStore.d.ts +3 -0
  159. package/dist/components/pageHeaderStore.js +3 -0
  160. package/dist/components/pathBreaks.js +2 -2
  161. package/dist/components/popupContainer.d.ts +17 -0
  162. package/dist/components/popupContainer.js +69 -0
  163. package/dist/components/relativeTime.d.ts +21 -10
  164. package/dist/components/relativeTime.js +50 -22
  165. package/dist/components/segmented.d.ts +18 -4
  166. package/dist/components/segmented.js +48 -6
  167. package/dist/components/typedDate.d.ts +5 -0
  168. package/dist/components/typedDate.js +50 -0
  169. package/dist/components/useClosingValue.d.ts +4 -0
  170. package/dist/components/useClosingValue.js +12 -0
  171. package/dist/components/useCopy.d.ts +6 -0
  172. package/dist/components/useCopy.js +3 -3
  173. package/dist/components/useNarrow.d.ts +2 -0
  174. package/dist/components/useNarrow.js +16 -0
  175. package/dist/editor/Editor.js +1 -1
  176. package/dist/editor/FormCodePanel.js +1 -1
  177. package/dist/editor/Monaco.js +1 -1
  178. package/dist/editor/workers.js +1 -1
  179. package/dist/file-explorer/FileExplorer.js +16 -15
  180. package/dist/file-explorer/FileExplorerContext.js +2 -2
  181. package/dist/file-explorer/FilePreview/Viewer.js +3 -3
  182. package/dist/file-explorer/FilePreview/index.js +2 -2
  183. package/dist/file-explorer/FileRowsWindow.js +3 -3
  184. package/dist/file-explorer/FileTable.js +1 -1
  185. package/dist/file-explorer/LoadMoreRow.js +1 -1
  186. package/dist/file-explorer/TreeView.js +5 -5
  187. package/dist/file-explorer/helpers.js +2 -2
  188. package/dist/file-explorer/toasts.js +2 -2
  189. package/dist/form/Fields/CheckboxGroupField.js +1 -1
  190. package/dist/form/Fields/DropdownField.js +1 -1
  191. package/dist/form/Fields/DurationField.js +2 -2
  192. package/dist/form/Fields/MultiDropdownField.js +1 -1
  193. package/dist/form/Fields/NumberField.js +1 -1
  194. package/dist/form/Fields/ObjectField.js +1 -1
  195. package/dist/form/Fields/RangeField.js +1 -1
  196. package/dist/form/Fields/StringField.js +1 -1
  197. package/dist/form/Form.js +3 -3
  198. package/dist/form/FormLayout.js +1 -1
  199. package/dist/form/Wizard/WizardContainer.js +1 -1
  200. package/dist/form/Wizard/WizardNavigation.js +1 -1
  201. package/dist/form/Wizard/WizardStepContent.js +1 -1
  202. package/dist/form/fieldRegistry.js +1 -1
  203. package/dist/form/formEditing.js +1 -1
  204. package/dist/format.d.ts +17 -7
  205. package/dist/format.js +39 -16
  206. package/dist/graph/AnnotationBanner.js +2 -2
  207. package/dist/graph/Connectors.js +5 -5
  208. package/dist/graph/DependencyGraph.js +10 -9
  209. package/dist/graph/GraphEditor.js +4 -4
  210. package/dist/graph/GraphEditorDialogs.js +13 -12
  211. package/dist/graph/GraphNode.js +1 -1
  212. package/dist/graph/InputDialog.js +5 -5
  213. package/dist/graph/InputsEditor.js +10 -9
  214. package/dist/graph/MatrixGroup.js +2 -2
  215. package/dist/graph/ProblemPageLink.d.ts +9 -0
  216. package/dist/graph/ProblemPageLink.js +21 -0
  217. package/dist/graph/ProblemsButton.js +26 -16
  218. package/dist/graph/Reveal.js +1 -1
  219. package/dist/graph/RunWarningsList.js +21 -12
  220. package/dist/graph/ShortcutsButton.js +25 -8
  221. package/dist/graph/StepSummaries.js +1 -1
  222. package/dist/graph/TreeView.js +2 -2
  223. package/dist/graph/WorkflowSettingsDialog.js +3 -3
  224. package/dist/graph/editorApi.d.ts +10 -0
  225. package/dist/graph/editorFields.js +3 -3
  226. package/dist/graph/editorPrimitives.js +1 -1
  227. package/dist/graph/editorRows.js +8 -6
  228. package/dist/graph/editorStrings.d.ts +1 -0
  229. package/dist/graph/editorStrings.js +1 -0
  230. package/dist/graph/fieldProblems.js +2 -2
  231. package/dist/graph/inputRefs.js +7 -6
  232. package/dist/graph/runWarnings.d.ts +12 -1
  233. package/dist/graph/stepWith.js +2 -2
  234. package/dist/index.d.ts +30 -21
  235. package/dist/index.js +20 -12
  236. package/dist/list/ActionToolbar.d.ts +4 -0
  237. package/dist/list/ActionToolbar.js +35 -12
  238. package/dist/list/ListFilterBar.d.ts +42 -0
  239. package/dist/list/ListFilterBar.js +789 -0
  240. package/dist/list/ListPage.d.ts +42 -4
  241. package/dist/list/ListPage.js +56 -22
  242. package/dist/list/ListTable.d.ts +27 -3
  243. package/dist/list/ListTable.js +48 -23
  244. package/dist/list/ListToolbar.d.ts +31 -0
  245. package/dist/list/ListToolbar.js +43 -0
  246. package/dist/list/ListViewControls.d.ts +8 -4
  247. package/dist/list/ListViewControls.js +22 -14
  248. package/dist/list/UserHoverCard.d.ts +8 -2
  249. package/dist/list/UserHoverCard.js +26 -13
  250. package/dist/list/filterModel.d.ts +122 -0
  251. package/dist/list/filterModel.js +308 -0
  252. package/dist/list/index.d.ts +6 -2
  253. package/dist/list/index.js +6 -2
  254. package/dist/list/listSearch.d.ts +1 -1
  255. package/dist/list/listSearch.js +8 -4
  256. package/dist/list/listView.d.ts +24 -2
  257. package/dist/list/listView.js +47 -45
  258. package/dist/list/rowMenu.d.ts +10 -5
  259. package/dist/list/rowMenu.js +17 -7
  260. package/dist/list/useListFilters.d.ts +68 -0
  261. package/dist/list/useListFilters.js +252 -0
  262. package/dist/logviewer/LogViewer.js +4 -4
  263. package/dist/styles/theme.css +94 -0
  264. package/dist/styles.css +1 -1
  265. package/package.json +11 -11
  266. package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
  267. package/dist/components/fullscreen.d.ts +0 -4
  268. package/dist/components/fullscreen.js +0 -18
@@ -1,23 +1,99 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { toneFill, toneSurface, toneText } from "./tone.js";
3
- import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useStrings } from "./Provider.js";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { useRender } from "@base-ui/react/use-render";
4
6
  //#region src/components/StatusBadge.tsx
7
+ var emphasisClasses = {
8
+ soft: {
9
+ ...toneSurface,
10
+ accent: "bg-(--theme-accent)/10 text-(--theme-link) border-(--theme-accent)/25"
11
+ },
12
+ solid: {
13
+ default: "bg-(--theme-app) text-(--theme-app-bg) border-transparent",
14
+ success: "bg-(--theme-success-text) text-(--theme-app-bg) border-transparent",
15
+ warning: "bg-(--theme-warning-text) text-(--theme-app-bg) border-transparent",
16
+ error: "bg-(--theme-error-text) text-(--theme-app-bg) border-transparent",
17
+ info: "bg-(--theme-info-text) text-(--theme-app-bg) border-transparent",
18
+ muted: "bg-(--theme-muted-text-color) text-(--theme-app-bg) border-transparent",
19
+ accent: "bg-(--theme-accent) text-(--theme-accent-text) border-transparent"
20
+ },
21
+ outline: {
22
+ default: "bg-transparent text-(--theme-app) border-(--theme-border)",
23
+ success: "bg-transparent text-(--theme-success-text) border-(--theme-success)/40",
24
+ warning: "bg-transparent text-(--theme-warning-text) border-(--theme-warning)/40",
25
+ error: "bg-transparent text-(--theme-error-text) border-(--theme-error)/40",
26
+ info: "bg-transparent text-(--theme-info-text) border-(--theme-info)/40",
27
+ muted: "bg-transparent text-(--theme-muted-text-color) border-(--theme-border)",
28
+ accent: "bg-transparent text-(--theme-link) border-(--theme-accent)/40"
29
+ }
30
+ };
31
+ var dotFill = {
32
+ ...toneFill,
33
+ accent: "bg-(--theme-accent)"
34
+ };
35
+ var sizeClasses = {
36
+ sm: "gap-1 py-px text-[10px] leading-3.5",
37
+ md: "gap-1.5 py-0.5 text-[11px] leading-4"
38
+ };
39
+ var padClasses = {
40
+ pill: {
41
+ sm: "px-1.5",
42
+ md: "px-2"
43
+ },
44
+ tag: {
45
+ sm: "px-1",
46
+ md: "px-1.5"
47
+ }
48
+ };
49
+ var shapeClasses = {
50
+ pill: "rounded-full",
51
+ tag: "rounded-[4px]"
52
+ };
5
53
  /** A line of text in a variant's color, for a status that stands beside a
6
54
  * dot rather than inside a badge. */
7
55
  function statusTextClass(variant) {
8
- return toneText[variant];
56
+ return variant === "accent" ? "text-(--theme-link)" : toneText[variant];
9
57
  }
10
- function StatusBadge({ children, variant = "default", pulse = false, dot = false, size = "sm", className }) {
58
+ /** A short status or label in a tone, such as "Running" or "Preview". */
59
+ function StatusBadge({ children, variant = "default", emphasis = "soft", shape = "pill", caps = false, truncate = false, pulse = false, dot = false, size = "sm", render, className }) {
60
+ const indicator = emphasis === "solid" ? "bg-current" : dotFill[variant];
61
+ return useRender({
62
+ render: render ?? /* @__PURE__ */ jsx("span", {}),
63
+ props: {
64
+ className: cx("inline-flex items-center border font-medium no-underline", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--theme-focus)", sizeClasses[size], padClasses[shape][size], shapeClasses[shape], caps && "uppercase tracking-wider", truncate && "min-w-0 max-w-full", emphasisClasses[emphasis][variant], className),
65
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
66
+ pulse && /* @__PURE__ */ jsxs("span", {
67
+ className: "relative flex h-2 w-2 shrink-0",
68
+ children: [/* @__PURE__ */ jsx("span", { className: cx("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 motion-reduce:hidden", indicator) }), /* @__PURE__ */ jsx("span", { className: cx("relative inline-flex h-2 w-2 rounded-full", indicator) })]
69
+ }),
70
+ dot && !pulse && /* @__PURE__ */ jsx("span", { className: cx("inline-flex h-1.5 w-1.5 shrink-0 rounded-full", indicator) }),
71
+ truncate ? /* @__PURE__ */ jsx("span", {
72
+ className: "min-w-0 truncate",
73
+ children
74
+ }) : children
75
+ ] })
76
+ }
77
+ });
78
+ }
79
+ var countSizeClasses = {
80
+ sm: "min-w-3.5 px-1 text-[10px] leading-3.5",
81
+ md: "min-w-4 px-1.5 text-[11px] leading-4"
82
+ };
83
+ /** A number beside a link or tab, such as unread messages or items waiting,
84
+ * capped at `max` so a big count doesn't widen what it sits in. */
85
+ function CountBadge({ count, max = 99, variant = "accent", emphasis = "solid", size = "md", label, showZero = false, locale, className }) {
86
+ const { common } = useStrings();
87
+ if (count <= 0 && !showZero) return null;
88
+ const number = new Intl.NumberFormat(locale);
89
+ const text = count > max ? common.countOverflow(number.format(max)) : number.format(count);
11
90
  return /* @__PURE__ */ jsxs("span", {
12
- className: cx("inline-flex items-center gap-1.5 rounded-full border font-medium", size === "sm" && "px-2 py-0.5 text-[10px]", size === "md" && "px-2.5 py-1 text-xs", toneSurface[variant], className),
13
- children: [
14
- pulse && /* @__PURE__ */ jsxs("span", {
15
- className: "relative flex h-2 w-2",
16
- children: [/* @__PURE__ */ jsx("span", { className: cx("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 motion-reduce:hidden", toneFill[variant]) }), /* @__PURE__ */ jsx("span", { className: cx("relative inline-flex h-2 w-2 rounded-full", toneFill[variant]) })]
17
- }),
18
- dot && !pulse && /* @__PURE__ */ jsx("span", { className: cx("inline-flex h-1.5 w-1.5 rounded-full", toneFill[variant]) }),
19
- children
20
- ]
91
+ "data-count": count,
92
+ className: cx("inline-flex shrink-0 items-center justify-center rounded-full font-semibold tabular-nums", countSizeClasses[size], emphasisClasses[emphasis][variant], className),
93
+ children: [text, label && /* @__PURE__ */ jsxs("span", {
94
+ className: "sr-only",
95
+ children: [" ", label]
96
+ })]
21
97
  });
22
98
  }
23
99
  function StatusDot({ variant = "default", pulse = false, size = 8, className }) {
@@ -27,8 +103,8 @@ function StatusDot({ variant = "default", pulse = false, size = 8, className })
27
103
  width: size,
28
104
  height: size
29
105
  },
30
- children: [pulse && /* @__PURE__ */ jsx("span", { className: cx("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 motion-reduce:hidden", toneFill[variant]) }), /* @__PURE__ */ jsx("span", {
31
- className: cx("relative inline-flex rounded-full", toneFill[variant]),
106
+ children: [pulse && /* @__PURE__ */ jsx("span", { className: cx("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75 motion-reduce:hidden", dotFill[variant]) }), /* @__PURE__ */ jsx("span", {
107
+ className: cx("relative inline-flex rounded-full", dotFill[variant]),
32
108
  style: {
33
109
  width: size,
34
110
  height: size
@@ -37,4 +113,4 @@ function StatusDot({ variant = "default", pulse = false, size = 8, className })
37
113
  });
38
114
  }
39
115
  //#endregion
40
- export { StatusBadge, StatusDot, statusTextClass };
116
+ export { CountBadge, StatusBadge, StatusDot, statusTextClass };
@@ -1,7 +1,7 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { stickyClasses, useStickyTop } from "./stickyTop.js";
3
- import { jsx } from "react/jsx-runtime";
4
3
  import { useState } from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
5
  //#region src/components/StickyBar.tsx
6
6
  /** A strip of a page's own controls, such as a row of filters or a page's
7
7
  * actions, pinned as the page scrolls. It stops just below whatever its
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
2
  /** Which way a column sorts, as `aria-sort` names it. */
3
3
  export type SortDirection = 'ascending' | 'descending' | 'none';
4
- /** `comfortable` gives each row room; `compact` fits a short key and value list. */
5
- export type TableDensity = 'comfortable' | 'compact';
4
+ /** `comfortable` gives each row room; `compact` fits a short key and value
5
+ * list; `dense` sets small type in tight rows, for a table inside a card or
6
+ * a panel of figures. */
7
+ export type TableDensity = 'comfortable' | 'compact' | 'dense';
6
8
  interface ItemProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
7
9
  /** Lets the text wrap; by default a cell keeps to one line. */
8
10
  wrap?: boolean;
@@ -18,13 +20,29 @@ interface HeaderProps {
18
20
  sort?: SortDirection | undefined;
19
21
  /** Sorts by this column, or flips its direction. */
20
22
  onSort?: (() => void) | undefined;
21
- /** False sets the title in sentence case instead of small capitals. */
23
+ /** False sets the title in sentence case instead of small capitals. The
24
+ * table's `caps` by default. */
22
25
  caps?: boolean | undefined;
23
26
  }
24
27
  declare function Header({ children, className, style, colSpan, sort, onSort, caps }: HeaderProps): React.JSX.Element;
25
28
  interface TableProps {
26
29
  children?: React.ReactNode;
27
30
  density?: TableDensity | undefined;
31
+ /** Each column's width, in order, such as `['20%', '8rem', undefined]`; a
32
+ * column left undefined takes what the others leave. With
33
+ * `table-fixed` in `className`, the widths hold whatever the cells hold. */
34
+ columns?: readonly (string | undefined)[] | undefined;
35
+ /** False sets every header in sentence case, as a small table in a card
36
+ * reads best; a header's own `caps` still wins. */
37
+ caps?: boolean | undefined;
38
+ /** Drops the outer side padding of the first and last columns and the
39
+ * header's fill, for a table inside something already padded, such as a
40
+ * card: it reads as part of the card, its titles in line with the card's
41
+ * other content and set apart by the rule under them. */
42
+ flush?: boolean | undefined;
43
+ /** Rules a line between columns as well as rows, for a grid of figures to
44
+ * read across and down. */
45
+ columnDividers?: boolean | undefined;
28
46
  borderless?: boolean;
29
47
  /** When false, drops the theme-panel background. */
30
48
  panel?: boolean;
@@ -56,7 +74,7 @@ interface TableProps {
56
74
  }
57
75
  /** Rows of values under a row of column titles. Put `Table.Header`s first among
58
76
  * the children, then `<tr>`s of `Table.Item`s. */
59
- export declare function Table({ density, borderless, panel, className, wrapperClassName, tbodyClassName, theadClassName, children, roundedTop, rounded, roundedBottom, isLoading, stickyHeader, tableProps, }: TableProps): React.JSX.Element;
77
+ export declare function Table({ density, columns, caps, flush, columnDividers, borderless, panel, className, wrapperClassName, tbodyClassName, theadClassName, children, roundedTop, rounded, roundedBottom, isLoading, stickyHeader, tableProps, }: TableProps): React.JSX.Element;
60
78
  export declare namespace Table {
61
79
  export { Header };
62
80
  export { Item };
@@ -2,16 +2,27 @@ import { cx } from "../cx.js";
2
2
  import { SortDownIcon, SortUnsortedIcon, SortUpIcon } from "../icons.js";
3
3
  import { Loader } from "./Loader.js";
4
4
  import { useStickyTop } from "./stickyTop.js";
5
- import { jsx, jsxs } from "react/jsx-runtime";
6
5
  import React, { createContext, useContext, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/components/Table.tsx
8
8
  var DensityContext = createContext("comfortable");
9
+ var CapsContext = createContext(true);
10
+ var cellY = {
11
+ comfortable: "py-4",
12
+ compact: "py-1.5 text-sm",
13
+ dense: "py-1 text-xs"
14
+ };
15
+ var headerY = {
16
+ comfortable: "py-3",
17
+ compact: "py-2",
18
+ dense: "py-1.5"
19
+ };
9
20
  var cellX = "px-3";
10
21
  function Item({ wrap = false, children, className, ...tdProps }) {
11
22
  const density = useContext(DensityContext);
12
23
  return /* @__PURE__ */ jsx("td", {
13
24
  ...tdProps,
14
- className: cx(cellX, density === "compact" ? "py-1.5 text-sm" : "py-4", !wrap && "whitespace-nowrap", className),
25
+ className: cx(cellX, cellY[density], !wrap && "whitespace-nowrap", className),
15
26
  children
16
27
  });
17
28
  }
@@ -20,15 +31,16 @@ var sortIcons = {
20
31
  descending: SortDownIcon,
21
32
  none: SortUnsortedIcon
22
33
  };
23
- function Header({ children, className, style, colSpan, sort, onSort, caps = true }) {
34
+ function Header({ children, className, style, colSpan, sort, onSort, caps }) {
24
35
  const density = useContext(DensityContext);
36
+ const tableCaps = useContext(CapsContext);
25
37
  const Icon = sortIcons[sort ?? "none"];
26
38
  return /* @__PURE__ */ jsx("th", {
27
39
  scope: "col",
28
40
  colSpan,
29
41
  style,
30
42
  "aria-sort": onSort ? sort ?? "none" : void 0,
31
- className: cx(cellX, density === "compact" ? "py-2" : "py-3", "text-left text-xs font-medium", caps && "uppercase tracking-wider", className),
43
+ className: cx(cellX, headerY[density], "text-left text-xs font-medium", (caps ?? tableCaps) && "uppercase tracking-wider", className),
32
44
  children: onSort ? /* @__PURE__ */ jsxs("button", {
33
45
  type: "button",
34
46
  onClick: onSort,
@@ -46,32 +58,42 @@ var isHeaderChild = (child) => {
46
58
  };
47
59
  /** Rows of values under a row of column titles. Put `Table.Header`s first among
48
60
  * the children, then `<tr>`s of `Table.Item`s. */
49
- function Table({ density = "comfortable", borderless = false, panel = true, className, wrapperClassName = "", tbodyClassName = "divide-y", theadClassName = "theme-muted-panel", children, roundedTop = false, rounded = false, roundedBottom = false, isLoading = false, stickyHeader = false, tableProps }) {
61
+ function Table({ density = "comfortable", columns, caps = true, flush = false, columnDividers = false, borderless = false, panel = true, className, wrapperClassName = "", tbodyClassName = "divide-y", theadClassName, children, roundedTop = false, rounded = false, roundedBottom = false, isLoading = false, stickyHeader = false, tableProps }) {
50
62
  const childList = React.Children.toArray(children);
51
63
  const [thead, setThead] = useState(null);
52
64
  useStickyTop(thead, stickyHeader && !isLoading);
53
65
  const hasOverflow = wrapperClassName.split(" ").some((cls) => cls.startsWith("overflow-"));
54
66
  return /* @__PURE__ */ jsx(DensityContext, {
55
67
  value: density,
56
- children: /* @__PURE__ */ jsx("div", {
57
- "aria-busy": isLoading,
58
- className: cx("flex w-full flex-col", !hasOverflow && !stickyHeader && "overflow-hidden", wrapperClassName),
59
- children: isLoading ? /* @__PURE__ */ jsx("div", {
60
- className: "flex h-32 items-center justify-center",
61
- children: /* @__PURE__ */ jsx(Loader, {})
62
- }) : /* @__PURE__ */ jsx("div", {
63
- className: cx("inline-block min-w-full align-middle", !stickyHeader && "overflow-x-auto", panel && "theme-panel", !borderless && "border theme-border", rounded && "rounded-xl", roundedBottom && "rounded-b-xl", roundedTop && "rounded-t-xl"),
64
- children: /* @__PURE__ */ jsxs("table", {
65
- className: cx("min-w-full divide-y theme-border", className),
66
- ...tableProps,
67
- children: [/* @__PURE__ */ jsx("thead", {
68
- ref: setThead,
69
- className: cx(stickyHeader && "sticky top-0 z-(--layer-sticky) border-b-0 shadow-[inset_0_-1px_0_var(--theme-border)]", theadClassName),
70
- children: /* @__PURE__ */ jsx("tr", { children: childList.filter(isHeaderChild) })
71
- }), /* @__PURE__ */ jsx("tbody", {
72
- className: tbodyClassName,
73
- children: childList.filter((c) => !isHeaderChild(c))
74
- })]
68
+ children: /* @__PURE__ */ jsx(CapsContext, {
69
+ value: caps,
70
+ children: /* @__PURE__ */ jsx("div", {
71
+ "aria-busy": isLoading,
72
+ className: cx("flex w-full flex-col", !hasOverflow && !stickyHeader && "overflow-hidden", wrapperClassName),
73
+ children: isLoading ? /* @__PURE__ */ jsx("div", {
74
+ className: "flex h-32 items-center justify-center",
75
+ children: /* @__PURE__ */ jsx(Loader, {})
76
+ }) : /* @__PURE__ */ jsx("div", {
77
+ className: cx("inline-block min-w-full align-middle", !stickyHeader && "overflow-x-auto", panel && "theme-panel", !borderless && "border theme-border", rounded && "rounded-xl", roundedBottom && "rounded-b-xl", roundedTop && "rounded-t-xl"),
78
+ children: /* @__PURE__ */ jsxs("table", {
79
+ className: cx("min-w-full divide-y theme-border", flush && "[&_td:first-child]:pl-0 [&_th:first-child]:pl-0 [&_td:last-child]:pr-0 [&_th:last-child]:pr-0", columnDividers && "[&_tr]:divide-x [&_tr]:divide-(--theme-border)", className),
80
+ ...tableProps,
81
+ children: [
82
+ columns && /* @__PURE__ */ jsx("colgroup", {
83
+ className: "border-0",
84
+ children: columns.map((width, i) => /* @__PURE__ */ jsx("col", { style: width ? { width } : void 0 }, i))
85
+ }),
86
+ /* @__PURE__ */ jsx("thead", {
87
+ ref: setThead,
88
+ className: cx(stickyHeader && "sticky top-0 z-(--layer-sticky) border-b-0 shadow-[inset_0_-1px_0_var(--theme-border)]", theadClassName ?? (flush ? "text-(--theme-muted-text-color)" : "theme-muted-panel")),
89
+ children: /* @__PURE__ */ jsx("tr", { children: childList.filter(isHeaderChild) })
90
+ }),
91
+ /* @__PURE__ */ jsx("tbody", {
92
+ className: tbodyClassName,
93
+ children: childList.filter((c) => !isHeaderChild(c))
94
+ })
95
+ ]
96
+ })
75
97
  })
76
98
  })
77
99
  })
@@ -1,11 +1,16 @@
1
1
  import { type ReactNode } from 'react';
2
- import { type SegmentLayout } from './segmented.js';
2
+ import { type SegmentLayout, type SegmentTone } from './segmented.js';
3
3
  export interface TabItem<Value extends string = string> {
4
4
  value: Value;
5
5
  label: string;
6
6
  icon?: ReactNode;
7
7
  /** Beside the label, or in the corner when only the icon shows. */
8
8
  badge?: ReactNode;
9
+ /** How many things the view holds, as a pill after the label. */
10
+ count?: number | undefined;
11
+ /** Colors the label and the count, for a view that needs attention, such
12
+ * as what failed or what has been left behind. */
13
+ tone?: SegmentTone | undefined;
9
14
  /** Navigates instead of showing a panel, for tabs that are pages. */
10
15
  href?: string;
11
16
  disabled?: boolean;
@@ -20,8 +25,8 @@ export interface TabsProps<Value extends string> extends SegmentLayout {
20
25
  defaultValue?: Value | undefined;
21
26
  onValueChange?: ((value: Value) => void) | undefined;
22
27
  /** `line` underlines the current tab across a page; `segmented` matches a
23
- * SegmentedControl, for a few views in a small space. `fill` and `iconOnly`
24
- * apply to `segmented`. */
28
+ * SegmentedControl, for a few views in a small space. `fill`, `iconOnly`
29
+ * and `size` apply to `segmented`. */
25
30
  variant?: 'line' | 'segmented' | undefined;
26
31
  /** At the tab row's far end, such as a picker for what the tabs show. */
27
32
  end?: ReactNode;
@@ -37,7 +42,7 @@ export interface TabsProps<Value extends string> extends SegmentLayout {
37
42
  /** Views of one thing, one shown at a time: panels below the tabs, or pages
38
43
  * the tabs link to. A lone tab would only repeat the page's title, so the row
39
44
  * hides unless there are two or more, or something at its end. */
40
- export declare function Tabs<Value extends string>({ label, items, value, defaultValue, onValueChange, variant, fill, iconOnly, end, sticky, className, testId, children, }: TabsProps<Value>): import("react").JSX.Element;
45
+ export declare function Tabs<Value extends string>({ label, items, value, defaultValue, onValueChange, variant, fill, iconOnly, size, end, sticky, className, testId, children, }: TabsProps<Value>): import("react").JSX.Element;
41
46
  /** One tab's content, shown while its tab is current. */
42
47
  export declare function TabPanel({ value, keepMounted, className, children, }: {
43
48
  value: string;
@@ -3,18 +3,23 @@ import { Tooltip } from "./Tooltip.js";
3
3
  import { AppLink } from "./AppLink.js";
4
4
  import { SegmentContent, segmentClassName, segmentGroupClassName } from "./segmented.js";
5
5
  import { useStickyTop } from "./stickyTop.js";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
6
  import { useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Tabs } from "@base-ui/react/tabs";
9
9
  //#region src/components/Tabs.tsx
10
+ var lineTone = {
11
+ warning: "text-(--theme-warning)",
12
+ danger: "text-(--theme-error-text)"
13
+ };
10
14
  /** Views of one thing, one shown at a time: panels below the tabs, or pages
11
15
  * the tabs link to. A lone tab would only repeat the page's title, so the row
12
16
  * hides unless there are two or more, or something at its end. */
13
- function Tabs$1({ label, items, value, defaultValue, onValueChange, variant = "line", fill = false, iconOnly = false, end, sticky, className, testId, children }) {
17
+ function Tabs$1({ label, items, value, defaultValue, onValueChange, variant = "line", fill = false, iconOnly = false, size = "md", end, sticky, className, testId, children }) {
14
18
  const segmented = variant === "segmented";
15
19
  const layout = {
16
20
  fill,
17
- iconOnly: segmented && iconOnly
21
+ iconOnly: segmented && iconOnly,
22
+ size
18
23
  };
19
24
  const [own, setOwn] = useState(defaultValue ?? items[0]?.value);
20
25
  const current = value ?? own;
@@ -30,7 +35,7 @@ function Tabs$1({ label, items, value, defaultValue, onValueChange, variant = "l
30
35
  nativeButton: false,
31
36
  render: /* @__PURE__ */ jsx(AppLink, { href: item.href })
32
37
  } : {},
33
- className: segmented ? segmentClassName(item.value === current, layout) : "flex cursor-pointer select-none items-center gap-1.5 whitespace-nowrap px-2 py-2 text-sm font-semibold theme-muted-text transition-colors hover:theme-text focus-visible:rounded-sm focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--theme-focus) data-active:text-(--theme-link) data-disabled:cursor-not-allowed data-disabled:opacity-50",
38
+ className: segmented ? segmentClassName(item.value === current, layout, item.tone) : cx("flex cursor-pointer select-none items-center gap-1.5 whitespace-nowrap px-2 py-2 text-sm font-semibold transition-colors focus-visible:rounded-sm focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--theme-focus) data-disabled:cursor-not-allowed data-disabled:opacity-50", item.tone ? lineTone[item.tone] : "theme-muted-text hover:theme-text data-active:text-(--theme-link)"),
34
39
  children: /* @__PURE__ */ jsx(SegmentContent, {
35
40
  ...item,
36
41
  iconOnly: layout.iconOnly
@@ -0,0 +1,49 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface TagStyle {
3
+ /** Monospace text, for a value read as code, such as a key=value label. */
4
+ mono?: boolean | undefined;
5
+ /** Small capitals with wider spacing, for a short code such as a region. */
6
+ caps?: boolean | undefined;
7
+ }
8
+ /** The one tag look: a pill with a hairline border and 11px text. Another component's chip,
9
+ * such as a picker's chosen value, uses it to look the same. */
10
+ export declare function tagClasses({ mono, caps }?: TagStyle): string;
11
+ export interface TagProps extends TagStyle {
12
+ children: ReactNode;
13
+ /** A dot before the text in this color, any CSS color, such as a label's own. */
14
+ color?: string | undefined;
15
+ /** Makes the tag a link, through the host's router. */
16
+ href?: string | undefined;
17
+ /** Adds an × that removes the tag, named by `removeLabel` ("Remove gpu" by default). */
18
+ onRemove?: (() => void) | undefined;
19
+ removeLabel?: string | undefined;
20
+ /** A hint on hover, such as the full text of a long tag. */
21
+ title?: string | undefined;
22
+ className?: string | undefined;
23
+ }
24
+ /** A tag or label on a thing: a pill with an optional color dot, a link with `href`, and an ×
25
+ * with `onRemove`. */
26
+ export declare function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps, className, }: TagProps): import("react").JSX.Element;
27
+ /** One tag of a `TagList`: a string is its own key and label. */
28
+ export type TagItem = string | {
29
+ key: string;
30
+ label: ReactNode;
31
+ color?: string | undefined;
32
+ href?: string | undefined;
33
+ };
34
+ export interface TagListProps extends TagStyle {
35
+ tags: readonly TagItem[];
36
+ /** `wrap`, the default, runs onto more lines; `clip` keeps one line and ends with a "+N"
37
+ * chip for the tags that don't fit, which lists them on hover or focus. */
38
+ overflow?: 'wrap' | 'clip' | undefined;
39
+ /** Shows at most this many, the rest in the "+N" chip. */
40
+ max?: number | undefined;
41
+ /** Adds an × to each tag; called with the tag's key. */
42
+ onRemove?: ((key: string) => void) | undefined;
43
+ className?: string | undefined;
44
+ }
45
+ /** A thing's tags as a list: wrapped, or on one line ending in "+N" for the rest. */
46
+ export declare function TagList({ tags, overflow, max, mono, caps, onRemove, className, }: TagListProps): import("react").JSX.Element;
47
+ /** Tags as the strings they hold, from an array or a comma-separated string: trimmed, empty
48
+ * ones dropped, each once. Anything else is no tags. */
49
+ export declare function normalizeTags(tags: unknown): string[];
@@ -0,0 +1,199 @@
1
+ import { cx } from "../cx.js";
2
+ import { RemoveIcon } from "../icons.js";
3
+ import { useLocale, useStrings } from "./Provider.js";
4
+ import { AppLink } from "./AppLink.js";
5
+ import { Popover } from "./Popover.js";
6
+ import { useLayoutEffect, useRef, useState } from "react";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/components/Tag.tsx
9
+ /** The one tag look: a pill with a hairline border and 11px text. Another component's chip,
10
+ * such as a picker's chosen value, uses it to look the same. */
11
+ function tagClasses({ mono = false, caps = false } = {}) {
12
+ return cx("inline-flex h-5 max-w-full min-w-0 shrink-0 items-center gap-1 rounded-full border border-(--theme-border) px-2 text-[11px] leading-none whitespace-nowrap text-(--theme-app)", mono ? "font-mono" : "font-sans", caps && "text-[10px] font-medium tracking-wide uppercase");
13
+ }
14
+ /** A tag or label on a thing: a pill with an optional color dot, a link with `href`, and an ×
15
+ * with `onRemove`. */
16
+ function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps, className }) {
17
+ const { dropdown } = useStrings();
18
+ const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [color && /* @__PURE__ */ jsx("span", {
19
+ "aria-hidden": "true",
20
+ className: "size-2 shrink-0 rounded-full",
21
+ style: { background: color }
22
+ }), /* @__PURE__ */ jsx("span", {
23
+ className: "min-w-0 truncate py-px",
24
+ children
25
+ })] });
26
+ const classes = cx(tagClasses({
27
+ mono,
28
+ caps
29
+ }), className);
30
+ const hover = "transition-colors hover:bg-(--theme-hover)";
31
+ if (!onRemove) return href ? /* @__PURE__ */ jsx(AppLink, {
32
+ href,
33
+ title,
34
+ className: cx(classes, hover, "no-underline"),
35
+ children: content
36
+ }) : /* @__PURE__ */ jsx("span", {
37
+ title,
38
+ className: classes,
39
+ children: content
40
+ });
41
+ const name = removeLabel ?? dropdown.removeValue(typeof children === "string" ? children : "");
42
+ return /* @__PURE__ */ jsxs("span", {
43
+ title,
44
+ className: cx(classes, "pr-0.5"),
45
+ children: [href ? /* @__PURE__ */ jsx(AppLink, {
46
+ href,
47
+ className: "inline-flex min-w-0 items-center gap-1 text-inherit no-underline hover:underline",
48
+ children: content
49
+ }) : content, /* @__PURE__ */ jsx("button", {
50
+ type: "button",
51
+ onClick: onRemove,
52
+ "aria-label": name.trim(),
53
+ className: "flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-full text-(--theme-muted-text-color) hover:bg-(--theme-hover) hover:text-(--theme-app) focus-visible:outline-2 focus-visible:outline-(--theme-focus)",
54
+ children: /* @__PURE__ */ jsx(RemoveIcon, {
55
+ "aria-hidden": "true",
56
+ className: "size-2.5"
57
+ })
58
+ })]
59
+ });
60
+ }
61
+ var normal = (tag) => typeof tag === "string" ? {
62
+ key: tag,
63
+ label: tag
64
+ } : tag;
65
+ var text = (tag) => typeof tag.label === "string" ? tag.label : tag.key;
66
+ /** A thing's tags as a list: wrapped, or on one line ending in "+N" for the rest. */
67
+ function TagList({ tags, overflow = "wrap", max, mono, caps, onRemove, className }) {
68
+ const items = tags.map(normal);
69
+ const clip = overflow === "clip";
70
+ const capped = max === void 0 ? items.length : Math.max(0, Math.min(max, items.length));
71
+ const [fit, setFit] = useState(capped);
72
+ const list = useRef(null);
73
+ const measure = useRef(null);
74
+ const signature = items.map((item) => item.key).join("\n");
75
+ useLayoutEffect(() => {
76
+ const row = list.current;
77
+ const copy = measure.current;
78
+ if (!clip || !row || !copy) return;
79
+ const place = () => {
80
+ const width = row.clientWidth;
81
+ const chips = [...copy.children];
82
+ const more = chips.pop();
83
+ const gap = 4;
84
+ const widths = chips.slice(0, capped).map((chip) => chip.offsetWidth);
85
+ const total = widths.reduce((sum, w, i) => sum + w + (i ? gap : 0), 0);
86
+ if (capped === items.length && total <= width) {
87
+ setFit(capped);
88
+ return;
89
+ }
90
+ const room = width - (more?.offsetWidth ?? 0) - gap;
91
+ let used = 0;
92
+ let count = 0;
93
+ for (const w of widths) {
94
+ const next = used + (count ? gap : 0) + w;
95
+ if (next > room) break;
96
+ used = next;
97
+ count++;
98
+ }
99
+ setFit(count);
100
+ };
101
+ place();
102
+ if (typeof ResizeObserver === "undefined") return;
103
+ const observer = new ResizeObserver(place);
104
+ observer.observe(row);
105
+ return () => observer.disconnect();
106
+ }, [
107
+ clip,
108
+ capped,
109
+ signature,
110
+ items.length
111
+ ]);
112
+ const shown = items.slice(0, clip ? fit : capped);
113
+ const hidden = items.slice(shown.length);
114
+ const style = {
115
+ mono,
116
+ caps
117
+ };
118
+ const tag = (item) => /* @__PURE__ */ jsx(Tag, {
119
+ ...style,
120
+ color: item.color,
121
+ href: item.href,
122
+ onRemove: onRemove ? () => onRemove(item.key) : void 0,
123
+ children: item.label
124
+ });
125
+ return /* @__PURE__ */ jsxs("div", {
126
+ className: cx("relative min-w-0", clip && "overflow-hidden", className),
127
+ children: [/* @__PURE__ */ jsxs("ul", {
128
+ ref: list,
129
+ className: cx("m-0 flex min-w-0 list-none items-center gap-1 p-0", !clip && "flex-wrap"),
130
+ children: [shown.map((item) => /* @__PURE__ */ jsx("li", {
131
+ className: "flex min-w-0",
132
+ children: tag(item)
133
+ }, item.key)), hidden.length > 0 && /* @__PURE__ */ jsx("li", {
134
+ className: "flex shrink-0",
135
+ children: /* @__PURE__ */ jsx(MoreTags, {
136
+ tags: hidden,
137
+ ...style
138
+ })
139
+ })]
140
+ }), clip && /* @__PURE__ */ jsxs("div", {
141
+ ref: measure,
142
+ "aria-hidden": "true",
143
+ inert: true,
144
+ className: "pointer-events-none invisible absolute top-0 left-0 flex h-0 items-center gap-1 overflow-hidden",
145
+ children: [items.slice(0, capped).map((item) => /* @__PURE__ */ jsx("span", {
146
+ className: "flex shrink-0",
147
+ children: tag(item)
148
+ }, item.key)), /* @__PURE__ */ jsxs("span", {
149
+ className: tagClasses(style),
150
+ children: ["+", items.length]
151
+ })]
152
+ })]
153
+ });
154
+ }
155
+ /** "+N" for the tags a list leaves out: a button that shows them in a popover, which also
156
+ * opens while the pointer rests on it, and whose name lists them. */
157
+ function MoreTags({ tags, ...style }) {
158
+ const { common } = useStrings();
159
+ const locale = useLocale();
160
+ const names = tags.map(text);
161
+ const count = new Intl.NumberFormat(locale).format(tags.length);
162
+ return /* @__PURE__ */ jsx(Popover, {
163
+ openOnHover: true,
164
+ initialFocus: "popup",
165
+ "aria-label": common.moreCount(tags.length),
166
+ className: "max-w-64 p-2",
167
+ trigger: /* @__PURE__ */ jsxs("button", {
168
+ type: "button",
169
+ "aria-label": `${common.moreCount(tags.length)}: ${names.join(", ")}`,
170
+ className: cx(tagClasses(style), "cursor-pointer text-(--theme-muted-text-color) tabular-nums hover:bg-(--theme-hover) hover:text-(--theme-app) focus-visible:outline-2 focus-visible:outline-(--theme-focus) data-popup-open:bg-(--theme-hover)"),
171
+ children: ["+", count]
172
+ }),
173
+ children: /* @__PURE__ */ jsx("ul", {
174
+ className: "m-0 flex list-none flex-wrap gap-1 p-0",
175
+ children: tags.map((tag) => /* @__PURE__ */ jsx("li", {
176
+ className: "flex min-w-0",
177
+ children: /* @__PURE__ */ jsx(Tag, {
178
+ ...style,
179
+ color: tag.color,
180
+ href: tag.href,
181
+ children: tag.label
182
+ })
183
+ }, tag.key))
184
+ })
185
+ });
186
+ }
187
+ /** Tags as the strings they hold, from an array or a comma-separated string: trimmed, empty
188
+ * ones dropped, each once. Anything else is no tags. */
189
+ function normalizeTags(tags) {
190
+ const list = Array.isArray(tags) ? tags : typeof tags === "string" ? tags.split(",") : [];
191
+ const seen = /* @__PURE__ */ new Set();
192
+ for (const tag of list) if (typeof tag === "string" || typeof tag === "number") {
193
+ const value = String(tag).trim();
194
+ if (value) seen.add(value);
195
+ }
196
+ return [...seen];
197
+ }
198
+ //#endregion
199
+ export { Tag, TagList, normalizeTags, tagClasses };
@@ -17,8 +17,8 @@ import { Switch } from "./Switch.js";
17
17
  import { Select } from "./Select.js";
18
18
  import { SegmentedControl } from "./SegmentedControl.js";
19
19
  import { Slider } from "./Slider.js";
20
- import { jsx, jsxs } from "react/jsx-runtime";
21
20
  import { useId, useMemo, useState } from "react";
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
22
  //#region src/components/ThemeEditor.tsx
23
23
  /**
24
24
  * Edits a theme: a background, an accent and a contrast for light and for
@@ -5,8 +5,8 @@ import { Loader } from "./Loader.js";
5
5
  import { toneIcon } from "./tone.js";
6
6
  import { useStrings } from "./Provider.js";
7
7
  import { AppLink } from "./AppLink.js";
8
- import { jsx, jsxs } from "react/jsx-runtime";
9
8
  import { useEffect, useId, useSyncExternalStore } from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
10
  import { Toast } from "@base-ui/react/toast";
11
11
  //#region src/components/Toast.tsx
12
12
  function actionFields(action) {