@parallelworks/ui 0.18.0 → 0.19.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 (161) hide show
  1. package/README.md +86 -6
  2. package/dist/ai/adapter/memoryStore.d.ts +8 -0
  3. package/dist/ai/adapter/mock/index.d.ts +2 -0
  4. package/dist/ai/adapter/mock/index.js +81 -0
  5. package/dist/ai/adapter/mock/mockAdapter.d.ts +4 -0
  6. package/dist/ai/adapter/openai/createOpenAIStream.d.ts +9 -0
  7. package/dist/ai/adapter/openai/errors.d.ts +3 -0
  8. package/dist/ai/adapter/openai/index.d.ts +6 -0
  9. package/dist/ai/adapter/openai/index.js +334 -0
  10. package/dist/ai/adapter/openai/openaiAdapter.d.ts +9 -0
  11. package/dist/ai/adapter/openai/responses.d.ts +4 -0
  12. package/dist/ai/adapter/openai/sse.d.ts +24 -0
  13. package/dist/ai/adapter/openai/wire.d.ts +40 -0
  14. package/dist/ai/adapter/types.d.ts +131 -0
  15. package/dist/ai/agent/classifier.d.ts +2 -0
  16. package/dist/ai/agent/diff.d.ts +35 -0
  17. package/dist/ai/agent/diffHighlight.d.ts +6 -0
  18. package/dist/ai/agent/index.d.ts +6 -0
  19. package/dist/ai/agent/permissions.d.ts +11 -0
  20. package/dist/ai/agent/toolfmt.d.ts +18 -0
  21. package/dist/ai/agent/transcript.d.ts +38 -0
  22. package/dist/ai/ai.css +325 -0
  23. package/dist/ai/components/AllocationSelector.d.ts +4 -0
  24. package/dist/ai/components/AttachmentManager.d.ts +1 -0
  25. package/dist/ai/components/AttachmentUpload.d.ts +24 -0
  26. package/dist/ai/components/BlockedGroupBanner.d.ts +1 -0
  27. package/dist/ai/components/BranchNavigator.d.ts +8 -0
  28. package/dist/ai/components/ChatEmptyState.d.ts +3 -0
  29. package/dist/ai/components/ChatInput.d.ts +45 -0
  30. package/dist/ai/components/ChatLayout.d.ts +5 -0
  31. package/dist/ai/components/ChatMessage.d.ts +24 -0
  32. package/dist/ai/components/ChatMessageList.d.ts +35 -0
  33. package/dist/ai/components/ChatSidebar.d.ts +5 -0
  34. package/dist/ai/components/ChatThread.d.ts +3 -0
  35. package/dist/ai/components/ComposerChrome.d.ts +18 -0
  36. package/dist/ai/components/ComposerFrame.d.ts +16 -0
  37. package/dist/ai/components/DragOverlay.d.ts +5 -0
  38. package/dist/ai/components/ModelSelector.d.ts +6 -0
  39. package/dist/ai/components/ShareDialog.d.ts +11 -0
  40. package/dist/ai/components/SlashMenu.d.ts +28 -0
  41. package/dist/ai/components/agent/ActivityLine.d.ts +21 -0
  42. package/dist/ai/components/agent/AgentMessageParts.d.ts +54 -0
  43. package/dist/ai/components/agent/PermissionPicker.d.ts +11 -0
  44. package/dist/ai/components/attachmentKeys.d.ts +4 -0
  45. package/dist/ai/core/ChatProvider.d.ts +66 -0
  46. package/dist/ai/core/KeyboardShortcutsProvider.d.ts +5 -0
  47. package/dist/ai/core/chatReducer.d.ts +114 -0
  48. package/dist/ai/core/config.d.ts +47 -0
  49. package/dist/ai/core/events.d.ts +1 -0
  50. package/dist/ai/core/exportConversation.d.ts +4 -0
  51. package/dist/ai/core/greeting.d.ts +3 -0
  52. package/dist/ai/core/parts.d.ts +11 -0
  53. package/dist/ai/core/useDragDrop.d.ts +14 -0
  54. package/dist/ai/core/useKeyboardShortcuts.d.ts +12 -0
  55. package/dist/ai/core/useSSEStream.d.ts +34 -0
  56. package/dist/ai/index.d.ts +40 -0
  57. package/dist/ai/index.js +6645 -0
  58. package/dist/ai/strings.d.ts +167 -0
  59. package/dist/ai/types.d.ts +236 -0
  60. package/dist/ai/ui/Avatar.d.ts +8 -0
  61. package/dist/ai/ui/Dropdown.d.ts +31 -0
  62. package/dist/ai/ui/Markdown.d.ts +8 -0
  63. package/dist/ai/ui/Notice.d.ts +21 -0
  64. package/dist/ai/ui/buttons.d.ts +1 -0
  65. package/dist/ai/ui/codeBreaks.d.ts +16 -0
  66. package/dist/ai/ui/prose.d.ts +1 -0
  67. package/dist/ai/ui/sidebar.d.ts +81 -0
  68. package/dist/ai/utils.d.ts +4 -0
  69. package/dist/assets/{editor.worker-CuBRIwi-.js → editor.worker-BmHUIVG4.js} +194 -163
  70. package/dist/assets/{json.worker-8ytS02vz.js → json.worker-D8bJ92nw.js} +384 -338
  71. package/dist/assets/{yaml.worker-DJC-VUcv.js → yaml.worker-C4gyGAko.js} +15657 -13823
  72. package/dist/chunks/{CodePreviewEditor-4lx3kE-w.js → CodePreviewEditor-YqQBz1YL.js} +1 -1
  73. package/dist/chunks/ConfirmModal-aOR4O1QE.js +79 -0
  74. package/dist/chunks/{FieldWrapper-Bef01EWO.js → FieldWrapper-Cszwg-4B.js} +3 -7
  75. package/dist/chunks/{CreateModal-BY6DLmur.js → Markdown-CM-FenNE.js} +19 -91
  76. package/dist/chunks/Monaco-DJ8MW-Dq.js +110 -0
  77. package/dist/chunks/{PageHeader-l49p1nM9.js → PageHeader-0kG_iypP.js} +67 -65
  78. package/dist/chunks/{Provider-Ghua9PkN.js → Provider-CDYurraE.js} +54 -20
  79. package/dist/chunks/{SwitchToggle-CHPP8uZQ.js → SwitchToggle-DShNdcao.js} +135 -132
  80. package/dist/chunks/{Table-4OHjdKdO.js → Table-DU2MW8xz.js} +5 -3
  81. package/dist/chunks/{Tooltip-Crxo_eU_.js → Tooltip-K5HJr4_p.js} +31 -4
  82. package/dist/chunks/duration-BOv8Phl8.js +17 -0
  83. package/dist/chunks/{editor-9_uVtUyy.js → editor-Dk1rbV75.js} +32 -18
  84. package/dist/chunks/focus-D_iZJTW1.js +7 -0
  85. package/dist/chunks/{list-qrojZZNv.js → list-CJm875m7.js} +377 -370
  86. package/dist/chunks/logviewer-BTKuQWzw.js +828 -0
  87. package/dist/chunks/memoryStore-CC91S5LP.js +152 -0
  88. package/dist/chunks/types-Yy0NrerF.js +11 -0
  89. package/dist/components/Avatar.d.ts +1 -1
  90. package/dist/components/Breadcrumbs.d.ts +1 -1
  91. package/dist/components/Dropdown.d.ts +1 -1
  92. package/dist/components/EmptyState.d.ts +1 -1
  93. package/dist/components/Indicator.d.ts +2 -2
  94. package/dist/components/Input.d.ts +1 -1
  95. package/dist/components/LetterBadge.d.ts +1 -1
  96. package/dist/components/MarkdownImpl.d.ts +1 -1
  97. package/dist/components/Provider.d.ts +16 -65
  98. package/dist/components/SettingRow.d.ts +1 -1
  99. package/dist/components/SettingsGroup.d.ts +1 -1
  100. package/dist/components/focus.d.ts +2 -0
  101. package/dist/components/keys.d.ts +12 -0
  102. package/dist/duration.d.ts +3 -0
  103. package/dist/editor/Monaco.d.ts +1 -20
  104. package/dist/editor/index.d.ts +2 -1
  105. package/dist/editor/index.js +2 -2
  106. package/dist/editor/yaml.d.ts +5 -0
  107. package/dist/engine.d.ts +98 -0
  108. package/dist/file-explorer/FileExplorer.d.ts +11 -2
  109. package/dist/file-explorer/FileExplorerContext.d.ts +1 -1
  110. package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +1 -1
  111. package/dist/file-explorer/FilePreview/previewType.d.ts +1 -1
  112. package/dist/file-explorer/FileRow.d.ts +1 -1
  113. package/dist/file-explorer/helpers.d.ts +4 -4
  114. package/dist/file-explorer/index.d.ts +3 -3
  115. package/dist/file-explorer/index.js +613 -647
  116. package/dist/file-explorer/lib/types.d.ts +6 -1
  117. package/dist/file-explorer/lib/utils.d.ts +1 -3
  118. package/dist/file-explorer/useNearEnd.d.ts +1 -1
  119. package/dist/form/FieldWrapper.d.ts +2 -2
  120. package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
  121. package/dist/form/Form.d.ts +4 -21
  122. package/dist/form/index.d.ts +11 -12
  123. package/dist/form/index.js +1488 -1611
  124. package/dist/form/lib.d.ts +6 -1
  125. package/dist/form/types/fieldComponentTypes.d.ts +3 -2
  126. package/dist/form/types/fieldTypes.d.ts +6 -20
  127. package/dist/form/useParsedOpts.d.ts +1 -1
  128. package/dist/form/utils/{slurmDuration.d.ts → duration.d.ts} +2 -2
  129. package/dist/{workflow → graph}/MatrixGroup.d.ts +2 -2
  130. package/dist/{workflow → graph}/index.d.ts +1 -1
  131. package/dist/{workflow → graph}/index.js +215 -180
  132. package/dist/{workflow → graph}/types.d.ts +3 -3
  133. package/dist/icons.d.ts +20 -21
  134. package/dist/index.d.ts +38 -40
  135. package/dist/index.js +347 -346
  136. package/dist/list/ListTable.d.ts +1 -1
  137. package/dist/list/ListViewControls.d.ts +2 -2
  138. package/dist/list/index.d.ts +9 -9
  139. package/dist/list/index.js +2 -2
  140. package/dist/logviewer/LogViewer.d.ts +1 -1
  141. package/dist/logviewer/index.d.ts +1 -0
  142. package/dist/logviewer/index.js +2 -742
  143. package/dist/logviewer/segments.d.ts +9 -0
  144. package/dist/styles/fonts.css +4 -4
  145. package/dist/styles/theme.css +6 -4
  146. package/dist/styles.css +1 -1
  147. package/dist/theme/index.js +2 -2
  148. package/package.json +38 -14
  149. package/LICENSE +0 -202
  150. package/dist/chunks/Monaco-C1POOdVv.js +0 -1046
  151. package/dist/editor/usesGitlab.d.ts +0 -97
  152. package/dist/form/Fields/BucketReadOnlyField.d.ts +0 -7
  153. package/dist/form/types/workflow-schema.d.ts +0 -1145
  154. package/dist/form/utils/pwResolver.d.ts +0 -18
  155. package/dist/chunks/{MarkdownImpl-Bq4QhrtA.js → MarkdownImpl-CyeTVvSt.js} +1 -1
  156. /package/dist/{workflow → graph}/AnnotationBanner.d.ts +0 -0
  157. /package/dist/{workflow → graph}/Collapse.d.ts +0 -0
  158. /package/dist/{workflow → graph}/DependencyGraph.d.ts +0 -0
  159. /package/dist/{workflow → graph}/JobSummary.d.ts +0 -0
  160. /package/dist/{workflow → graph}/Reveal.d.ts +0 -0
  161. /package/dist/{workflow → graph}/TreeView.d.ts +0 -0
package/dist/index.js CHANGED
@@ -1,148 +1,24 @@
1
- import { DEFAULT_PRESET, THEME_PRESETS, THEME_TOKENS, applyTheme, contrastRatio, deriveTheme, isDarkColor } from "./theme/index.js";
2
- import { a as usePersistedState, c as useRunFile, d as useWorkflowJsonResolver, i as useNotify, l as useSlots, n as useLink, o as useProvisionCorsRules, r as useNavigation, s as useRepoSuggestions, t as UIProvider, u as useStrings } from "./chunks/Provider-Ghua9PkN.js";
3
- import { C as useSetDefaultBreadcrumbs, E as avatarInitials, S as useSetBreadcrumbs, T as Avatar, a as HeaderAddButton, b as useClearBreadcrumbs, c as PageHeader, i as HeaderActionMenu, l as headerActionButtonClasses, n as BreadcrumbsRightAction, o as HeaderEditButton, r as BreadcrumbsTitleAction, s as HeaderRowMenu, t as BreadcrumbsActionButton, v as BreadcrumbProvider, w as AddMenu, x as useHideBreadcrumbs, y as useBreadcrumb } from "./chunks/PageHeader-l49p1nM9.js";
1
+ import { a as useOptionalWorkflowEngine, c as useRunFile, d as useWorkflowEngine, f as useWorkflowEngineLoader, i as useNotify, l as useSlots, n as useLink, o as usePersistedState, r as useNavigation, s as useProvisionCorsRules, t as UIProvider, u as useStrings } from "./chunks/Provider-CDYurraE.js";
4
2
  import { AlertIcon, AngleRightIcon, EyeIcon, EyeOffIcon, FilterIcon, LoaderIcon, SortDownIcon, SortUnsortedIcon, SortUpIcon } from "./icons.js";
5
- import { a as CreateModalSelect, c as ConfirmModal, d as primaryButtonClasses, f as Markdown, h as Callout, i as CreateModalField, l as dangerButtonClasses, m as CopyToClipboard, n as CreateModal, o as CreateModalSwitch, p as CopyCodeBlock, r as CreateModalAutocompleteField, s as CreateModalTextarea, t as CREATE_MODAL_SELECT_FILTER_THRESHOLD, u as ghostButtonClasses } from "./chunks/CreateModal-BY6DLmur.js";
6
- import { a as truncationTooltipProps, i as formatTooltipContent, n as TOOLTIP_ID, r as TooltipInfo, t as GlobalTooltip } from "./chunks/Tooltip-Crxo_eU_.js";
7
- import { a as filterOption, c as isCategory, i as DropdownCaret, l as isOption, n as StatusDot, r as statusTextClass, s as flattenOptions, t as StatusBadge, u as toggleSetMember } from "./chunks/StatusBadge-C1Blx_cW.js";
8
- import { a as UncontrolledCollapsiblePanel, c as Dropdown, i as MaybeCollapsible, n as SectionHeader, o as MultiSelectDropdown, r as ControlledCollapsiblePanel, s as CustomListbox, t as SwitchToggle } from "./chunks/SwitchToggle-CHPP8uZQ.js";
9
- import { t as Loader } from "./chunks/Loader-BXoXTZGc.js";
3
+ import { a as truncationTooltipProps, i as formatTooltipContent, n as TOOLTIP_ID, r as TooltipInfo, t as GlobalTooltip } from "./chunks/Tooltip-K5HJr4_p.js";
4
+ import { C as useSetDefaultBreadcrumbs, E as AddMenu, S as useSetBreadcrumbs, T as avatarInitials, a as HeaderAddButton, b as useClearBreadcrumbs, c as PageHeader, i as HeaderActionMenu, l as headerActionButtonClasses, n as BreadcrumbsRightAction, o as HeaderEditButton, r as BreadcrumbsTitleAction, s as HeaderRowMenu, t as BreadcrumbsActionButton, v as BreadcrumbProvider, w as Avatar, x as useHideBreadcrumbs, y as useBreadcrumb } from "./chunks/PageHeader-0kG_iypP.js";
10
5
  import { n as modalPanelClasses, t as BareModal } from "./chunks/BareModal-BtRiQezg.js";
11
- import { t as useCssIsDark } from "./chunks/useCssIsDark-DHr05dG2.js";
6
+ import { a as CreateModalField, c as CreateModalTextarea, d as Callout, i as CreateModalAutocompleteField, l as CopyCodeBlock, n as CREATE_MODAL_SELECT_FILTER_THRESHOLD, o as CreateModalSelect, r as CreateModal, s as CreateModalSwitch, t as Markdown, u as CopyToClipboard } from "./chunks/Markdown-CM-FenNE.js";
7
+ import { a as Dropdown, c as UncontrolledCollapsiblePanel, i as CustomListbox, n as SectionHeader, o as ControlledCollapsiblePanel, r as MultiSelectDropdown, s as MaybeCollapsible, t as SwitchToggle } from "./chunks/SwitchToggle-DShNdcao.js";
8
+ import { i as primaryButtonClasses, n as dangerButtonClasses, r as ghostButtonClasses, t as ConfirmModal } from "./chunks/ConfirmModal-aOR4O1QE.js";
9
+ import { a as filterOption, c as isCategory, i as DropdownCaret, l as isOption, n as StatusDot, r as statusTextClass, s as flattenOptions, t as StatusBadge, u as toggleSetMember } from "./chunks/StatusBadge-C1Blx_cW.js";
12
10
  import { t as Indicator } from "./chunks/Indicator-hBSIxOPK.js";
13
- import { t as SwitchToggleSmall } from "./chunks/SwitchToggleSmall-FcsaYSTs.js";
14
- import { n as Table, t as CompactTable } from "./chunks/Table-4OHjdKdO.js";
15
11
  import { t as RequiredMark } from "./chunks/RequiredMark-DfLdYbYZ.js";
12
+ import { t as Loader } from "./chunks/Loader-BXoXTZGc.js";
13
+ import { t as SwitchToggleSmall } from "./chunks/SwitchToggleSmall-FcsaYSTs.js";
14
+ import { n as Table, t as CompactTable } from "./chunks/Table-DU2MW8xz.js";
15
+ import { t as useCssIsDark } from "./chunks/useCssIsDark-DHr05dG2.js";
16
+ import { DEFAULT_PRESET, THEME_PRESETS, THEME_TOKENS, applyTheme, contrastRatio, deriveTheme, isDarkColor } from "./theme/index.js";
16
17
  import cx from "classnames";
17
18
  import React, { forwardRef, useEffect, useId, useState } from "react";
18
19
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
19
20
  import Skeleton from "react-loading-skeleton";
20
21
  import { DateTime } from "luxon";
21
- //#region src/components/DescriptionList.tsx
22
- function DescriptionListItem({ label, children, tooltip, className, ...props }) {
23
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("dt", {
24
- className: cx("text-[13px] leading-6 col-span-1 font-normal flex gap-1", className),
25
- children: [/* @__PURE__ */ jsx("span", {
26
- className: "min-w-0 truncate",
27
- children: label
28
- }), tooltip && /* @__PURE__ */ jsx(TooltipInfo, {
29
- className: "pr-2",
30
- text: tooltip
31
- })]
32
- }), /* @__PURE__ */ jsx("dd", {
33
- ...props,
34
- className: "flex text-[13px] leading-6 col-span-4 font-light items-center overflow-auto",
35
- children
36
- })] });
37
- }
38
- function DescriptionList({ children, title, separators = true, subtitle, className, wrapperClassName, vertical = false }) {
39
- return /* @__PURE__ */ jsxs("div", {
40
- className: cx("w-full", wrapperClassName),
41
- children: [
42
- title && /* @__PURE__ */ jsx("h2", {
43
- className: "text-sm font-semibold w-full mb-1",
44
- children: title
45
- }),
46
- subtitle && /* @__PURE__ */ jsx("div", {
47
- className: "max-w-2xl text-sm/6 w-full",
48
- children: subtitle
49
- }),
50
- /* @__PURE__ */ jsx("dl", {
51
- className: cx("grid", separators && "*:not-last-of-type:border-b-1", vertical ? "grid-cols-1" : "grid-cols-5 gap-x-4 gap-y-0.5 *:pb-0.5", className),
52
- children: Array.isArray(children) ? children.map((child, index) => /* @__PURE__ */ jsx(React.Fragment, { children: child }, index)) : children
53
- })
54
- ]
55
- });
56
- }
57
- //#endregion
58
- //#region src/components/EmptyState.tsx
59
- function EmptyState({ title, description, action, icon: Icon }) {
60
- return /* @__PURE__ */ jsxs("div", {
61
- className: "border-solid rounded-2xl border border-(--theme-border) theme-panel flex flex-col items-center justify-center py-12 px-6",
62
- children: [
63
- Icon && /* @__PURE__ */ jsx("div", {
64
- className: "w-10 h-10 rounded-full bg-(--theme-muted-panel-bg) flex items-center justify-center mb-3",
65
- children: /* @__PURE__ */ jsx(Icon, { className: "w-5 h-5 theme-muted-text" })
66
- }),
67
- /* @__PURE__ */ jsx("p", {
68
- className: "text-sm font-medium mb-1",
69
- children: title
70
- }),
71
- description && /* @__PURE__ */ jsx("p", {
72
- className: "text-xs theme-muted-text text-center max-w-xs",
73
- children: description
74
- }),
75
- action && /* @__PURE__ */ jsx("div", {
76
- className: "mt-4",
77
- children: action
78
- })
79
- ]
80
- });
81
- }
82
- //#endregion
83
- //#region src/components/Overview.tsx
84
- function Overview({ Icon, name, type, children, right, className, loading, iconSize = 75 }) {
85
- return /* @__PURE__ */ jsxs("div", {
86
- className: cx("flex items-center w-full", className),
87
- children: [
88
- loading ? /* @__PURE__ */ jsx(Skeleton, {
89
- width: iconSize,
90
- height: iconSize,
91
- circle: true
92
- }) : Icon,
93
- /* @__PURE__ */ jsx("div", {
94
- className: "flex flex-col min-w-0 flex-1",
95
- children: loading ? /* @__PURE__ */ jsx(Skeleton, {
96
- count: 2,
97
- height: 12
98
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
99
- /* @__PURE__ */ jsx("div", {
100
- className: "font-semibold text-xl tracking-tight",
101
- children: name
102
- }),
103
- type && /* @__PURE__ */ jsx("p", { children: type }),
104
- /* @__PURE__ */ jsx("div", {
105
- className: "flex flex-row gap-x-2",
106
- children
107
- })
108
- ] })
109
- }),
110
- right && /* @__PURE__ */ jsx("div", {
111
- className: "shrink-0 self-center",
112
- children: right
113
- })
114
- ]
115
- });
116
- }
117
- //#endregion
118
- //#region src/components/relativeTime.ts
119
- var TICK_MS = 3e4;
120
- function compactRelativeMinutes(minutes, t) {
121
- if (!Number.isFinite(minutes) || minutes < 1) return t.now;
122
- if (minutes < 60) return t.minutes(minutes);
123
- const hours = Math.floor(minutes / 60);
124
- if (hours < 24) return t.hours(hours);
125
- const days = Math.floor(hours / 24);
126
- if (days < 365) return t.days(days);
127
- return t.years(Math.floor(days / 365));
128
- }
129
- /** A clock that re-reads itself, so "5m" ages while the page sits open, with
130
- * the abbreviated form a row has room for. */
131
- function useRelativeTime() {
132
- const t = useStrings().time;
133
- const [now, setNow] = useState(() => DateTime.now());
134
- useEffect(() => {
135
- const id = setInterval(() => setNow(DateTime.now()), TICK_MS);
136
- return () => clearInterval(id);
137
- }, []);
138
- const compactMinutes = (minutes) => compactRelativeMinutes(minutes, t);
139
- return {
140
- now,
141
- compactMinutes,
142
- compact: (iso) => iso ? compactMinutes(Math.floor(now.diff(DateTime.fromISO(iso)).as("minutes"))) : ""
143
- };
144
- }
145
- //#endregion
146
22
  //#region src/components/Button.tsx
147
23
  var Button = forwardRef(({ children, variant = "primary", size = "default", loading, icon, iconPosition = "right", fullWidth = true, className = "", disabled, ...props }, ref) => {
148
24
  const baseClasses = "inline-flex items-center justify-center gap-2 font-semibold rounded-lg transition-all duration-200 cursor-pointer border-none mt-2 disabled:opacity-50 disabled:cursor-not-allowed";
@@ -248,97 +124,112 @@ function CardHeader({ badge, title, description, className = "", align = "center
248
124
  });
249
125
  }
250
126
  //#endregion
251
- //#region src/components/Input.tsx
252
- var Input = forwardRef(({ label, error, className = "", mono, type, required, ...props }, ref) => {
253
- const [showPassword, setShowPassword] = useState(false);
254
- const isPassword = type === "password";
255
- const inputType = isPassword && showPassword ? "text" : type;
256
- const generatedId = useId();
257
- const id = props.id ?? generatedId;
258
- const errorId = `${id}-error`;
259
- const describedBy = [props["aria-describedby"], error ? errorId : void 0].filter(Boolean).join(" ") || void 0;
260
- const inputClasses = cx("w-full py-3 px-4 bg-[var(--theme-muted-panel-bg)] border border-[var(--theme-border)] rounded-lg text-[var(--theme-app)] text-sm transition-all duration-200 box-border", "focus:outline-none focus:border-[var(--theme-element)] focus:shadow-[0_0_0_3px_color-mix(in_srgb,var(--theme-element)_25%,transparent)]", "placeholder:text-[var(--theme-muted-text-color)]", "disabled:bg-[var(--theme-muted-panel-bg)] disabled:text-[var(--theme-muted-text-color)] disabled:cursor-not-allowed disabled:opacity-70", mono && "font-mono text-[0.8125rem]", error && "border-[#ef4444]", isPassword && "pr-11", className);
127
+ //#region src/components/DescriptionList.tsx
128
+ function DescriptionListItem({ label, children, tooltip, className, ...props }) {
129
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("dt", {
130
+ className: cx("text-[13px] leading-6 col-span-1 font-normal flex gap-1", className),
131
+ children: [/* @__PURE__ */ jsx("span", {
132
+ className: "min-w-0 truncate",
133
+ children: label
134
+ }), tooltip && /* @__PURE__ */ jsx(TooltipInfo, {
135
+ className: "pr-2",
136
+ text: tooltip
137
+ })]
138
+ }), /* @__PURE__ */ jsx("dd", {
139
+ ...props,
140
+ className: "flex text-[13px] leading-6 col-span-4 font-light items-center overflow-auto",
141
+ children
142
+ })] });
143
+ }
144
+ function DescriptionList({ children, title, separators = true, subtitle, className, wrapperClassName, vertical = false }) {
261
145
  return /* @__PURE__ */ jsxs("div", {
262
- className: "flex flex-col gap-1.5",
146
+ className: cx("w-full", wrapperClassName),
263
147
  children: [
264
- label && /* @__PURE__ */ jsxs("label", {
265
- className: "text-[0.8125rem] font-medium text-[var(--theme-app)]",
266
- htmlFor: id,
267
- children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
148
+ title && /* @__PURE__ */ jsx("h2", {
149
+ className: "text-sm font-semibold w-full mb-1",
150
+ children: title
268
151
  }),
269
- /* @__PURE__ */ jsxs("div", {
270
- className: isPassword ? "relative w-full" : "",
271
- children: [/* @__PURE__ */ jsx("input", {
272
- ref,
273
- type: inputType,
274
- className: inputClasses,
275
- ...props,
276
- id,
277
- required,
278
- "aria-invalid": error ? true : props["aria-invalid"],
279
- "aria-describedby": describedBy
280
- }), isPassword && /* @__PURE__ */ jsx("button", {
281
- type: "button",
282
- className: "absolute right-3 top-1/2 -translate-y-1/2 bg-transparent border-none text-[var(--theme-muted-text-color)] cursor-pointer p-1 transition-colors duration-200 flex items-center justify-center hover:text-[var(--theme-app)]",
283
- onClick: () => setShowPassword(!showPassword),
284
- tabIndex: -1,
285
- "aria-label": showPassword ? "Hide password" : "Show password",
286
- children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, { className: "w-5 h-5" }) : /* @__PURE__ */ jsx(EyeIcon, { className: "w-5 h-5" })
287
- })]
152
+ subtitle && /* @__PURE__ */ jsx("div", {
153
+ className: "max-w-2xl text-sm/6 w-full",
154
+ children: subtitle
288
155
  }),
289
- error && /* @__PURE__ */ jsxs("span", {
290
- id: errorId,
291
- role: "alert",
292
- className: "text-[0.8125rem] text-[#ef4444] flex items-center gap-1.5",
293
- children: [/* @__PURE__ */ jsx(AlertIcon, { className: "w-4 h-4" }), error]
156
+ /* @__PURE__ */ jsx("dl", {
157
+ className: cx("grid", separators && "*:not-last-of-type:border-b-1", vertical ? "grid-cols-1" : "grid-cols-5 gap-x-4 gap-y-0.5 *:pb-0.5", className),
158
+ children: Array.isArray(children) ? React.Children.toArray(children) : children
294
159
  })
295
160
  ]
296
161
  });
297
- });
298
- Input.displayName = "Input";
299
- var Textarea = forwardRef(({ label, error, className = "", mono, required, ...props }, ref) => {
300
- const generatedId = useId();
301
- const id = props.id ?? generatedId;
302
- const errorId = `${id}-error`;
303
- const describedBy = [props["aria-describedby"], error ? errorId : void 0].filter(Boolean).join(" ") || void 0;
304
- const textareaClasses = cx("w-full py-3 px-4 bg-[var(--theme-muted-panel-bg)] border border-[var(--theme-border)] rounded-lg text-[var(--theme-app)] text-sm transition-all duration-200 box-border min-h-[140px] resize-y", "focus:outline-none focus:border-[var(--theme-element)] focus:shadow-[0_0_0_3px_color-mix(in_srgb,var(--theme-element)_25%,transparent)]", "placeholder:text-[var(--theme-muted-text-color)]", mono && "font-mono text-[0.8125rem]", error && "border-[#ef4444]", className);
162
+ }
163
+ //#endregion
164
+ //#region src/components/EmptyState.tsx
165
+ function EmptyState({ title, description, action, icon: Icon }) {
305
166
  return /* @__PURE__ */ jsxs("div", {
306
- className: "flex flex-col gap-1.5",
167
+ className: "border-solid rounded-2xl border border-(--theme-border) theme-panel flex flex-col items-center justify-center py-12 px-6",
307
168
  children: [
308
- label && /* @__PURE__ */ jsxs("label", {
309
- className: "text-[0.8125rem] font-medium text-[var(--theme-app)]",
310
- htmlFor: id,
311
- children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
169
+ Icon && /* @__PURE__ */ jsx("div", {
170
+ className: "w-10 h-10 rounded-full bg-(--theme-muted-panel-bg) flex items-center justify-center mb-3",
171
+ children: /* @__PURE__ */ jsx(Icon, { className: "w-5 h-5 theme-muted-text" })
312
172
  }),
313
- /* @__PURE__ */ jsx("textarea", {
314
- ref,
315
- className: textareaClasses,
316
- ...props,
317
- id,
318
- required,
319
- "aria-invalid": error ? true : props["aria-invalid"],
320
- "aria-describedby": describedBy
173
+ /* @__PURE__ */ jsx("p", {
174
+ className: "text-sm font-medium mb-1",
175
+ children: title
321
176
  }),
322
- error && /* @__PURE__ */ jsxs("span", {
323
- id: errorId,
324
- role: "alert",
325
- className: "text-[0.8125rem] text-[#ef4444] flex items-center gap-1.5",
326
- children: [/* @__PURE__ */ jsx(AlertIcon, { className: "w-4 h-4" }), error]
177
+ description && /* @__PURE__ */ jsx("p", {
178
+ className: "text-xs theme-muted-text text-center max-w-xs",
179
+ children: description
180
+ }),
181
+ action && /* @__PURE__ */ jsx("div", {
182
+ className: "mt-4",
183
+ children: action
327
184
  })
328
185
  ]
329
186
  });
330
- });
331
- Textarea.displayName = "Textarea";
332
- //#endregion
333
- //#region src/components/inputClasses.ts
334
- var inputClasses = "w-full rounded-md border theme-border bg-(--theme-input-bg) px-3 py-2 text-sm text-(--theme-panel) outline-none focus:ring-2 focus:ring-(--theme-element)";
187
+ }
335
188
  //#endregion
336
- //#region src/components/IconButton.tsx
337
- var IconButton = forwardRef(({ icon, label, variant = "default", size = "md", active = false, showTooltip = true, className, disabled, ...props }, ref) => {
338
- const sizeClasses = {
339
- sm: "w-7 h-7 [&_svg]:w-3.5 [&_svg]:h-3.5",
340
- md: "w-9 h-9 [&_svg]:w-4 [&_svg]:h-4",
341
- lg: "w-11 h-11 [&_svg]:w-5 [&_svg]:h-5"
189
+ //#region src/components/Filter.tsx
190
+ function Filter({ filter, setFilter, placeholder = "Filter...", pills, activePill, onPillChange }) {
191
+ return /* @__PURE__ */ jsxs("div", {
192
+ className: "flex items-center gap-2",
193
+ children: [/* @__PURE__ */ jsxs("div", {
194
+ className: "relative flex items-center",
195
+ children: [/* @__PURE__ */ jsx("input", {
196
+ placeholder,
197
+ type: "text",
198
+ value: filter,
199
+ onChange: (e) => setFilter(e.target.value.toLowerCase()),
200
+ className: "shadow-sm focus:ring-1 block w-full pr-3 sm:text-sm theme-border rounded-md py-1.5 pl-8"
201
+ }), /* @__PURE__ */ jsx("div", {
202
+ className: "absolute inset-y-0 left-0 flex py-2.5 pl-2",
203
+ children: /* @__PURE__ */ jsx(FilterIcon, {})
204
+ })]
205
+ }), pills && onPillChange && /* @__PURE__ */ jsx("div", {
206
+ className: "flex gap-1",
207
+ children: pills.map((pill) => /* @__PURE__ */ jsx("button", {
208
+ type: "button",
209
+ onClick: () => onPillChange(pill.value),
210
+ className: cx("px-2.5 py-1 text-xs rounded-full transition-colors border", activePill === pill.value ? "bg-(--theme-element) text-(--theme-element-text) border-transparent" : "theme-border theme-text hover:theme-hover"),
211
+ children: pill.label
212
+ }, pill.value))
213
+ })]
214
+ });
215
+ }
216
+ //#endregion
217
+ //#region src/components/FilterPill.tsx
218
+ function FilterPill({ active, onClick, children }) {
219
+ return /* @__PURE__ */ jsx("button", {
220
+ type: "button",
221
+ onClick,
222
+ className: cx("px-3 py-1.5 text-xs font-medium rounded-full border transition-all duration-200", active ? "bg-[var(--theme-panel-bg)] border-[var(--theme-border)] text-[var(--theme-panel)] shadow-sm" : "bg-transparent border-transparent text-[var(--theme-muted-text-color)] hover:text-[var(--theme-panel)] hover:bg-[var(--theme-hover)]"),
223
+ children
224
+ });
225
+ }
226
+ //#endregion
227
+ //#region src/components/IconButton.tsx
228
+ var IconButton = forwardRef(({ icon, label, variant = "default", size = "md", active = false, showTooltip = true, className, disabled, ...props }, ref) => {
229
+ const sizeClasses = {
230
+ sm: "w-7 h-7 [&_svg]:w-3.5 [&_svg]:h-3.5",
231
+ md: "w-9 h-9 [&_svg]:w-4 [&_svg]:h-4",
232
+ lg: "w-11 h-11 [&_svg]:w-5 [&_svg]:h-5"
342
233
  };
343
234
  const variantClasses = {
344
235
  default: cx("bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]", "hover:bg-[var(--theme-border)] hover:text-[var(--theme-app)]", active && "bg-[var(--theme-element)]/10 text-[var(--theme-element)]"),
@@ -362,22 +253,90 @@ var IconButton = forwardRef(({ icon, label, variant = "default", size = "md", ac
362
253
  });
363
254
  IconButton.displayName = "IconButton";
364
255
  //#endregion
365
- //#region src/components/Toggle.tsx
366
- function Toggle({ checked, onChange, disabled, label }) {
367
- return /* @__PURE__ */ jsxs("label", {
368
- role: "none",
369
- onClick: (e) => e.stopPropagation(),
370
- className: cx("relative inline-flex items-center", disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
371
- children: [/* @__PURE__ */ jsx("input", {
372
- type: "checkbox",
373
- className: "peer sr-only",
374
- checked,
375
- onChange: (e) => onChange(e.target.checked),
376
- disabled,
377
- "aria-label": label
378
- }), /* @__PURE__ */ jsx("div", { className: "h-6 w-11 rounded-full bg-(--theme-muted-panel-bg) transition-colors peer-checked:bg-(--theme-element) peer-focus-visible:ring-2 peer-focus-visible:ring-[color-mix(in_srgb,var(--theme-element)_25%,transparent)] after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white after:shadow-sm after:transition-transform after:content-[\"\"] motion-reduce:after:transition-none peer-checked:after:translate-x-5" })]
256
+ //#region src/components/Input.tsx
257
+ var Input = forwardRef(({ label, error, className = "", mono, type, required, ...props }, ref) => {
258
+ const [showPassword, setShowPassword] = useState(false);
259
+ const isPassword = type === "password";
260
+ const inputType = isPassword && showPassword ? "text" : type;
261
+ const generatedId = useId();
262
+ const id = props.id ?? generatedId;
263
+ const errorId = `${id}-error`;
264
+ const describedBy = [props["aria-describedby"], error ? errorId : void 0].filter(Boolean).join(" ") || void 0;
265
+ const inputClasses = cx("w-full py-3 px-4 bg-[var(--theme-muted-panel-bg)] border border-[var(--theme-border)] rounded-lg text-[var(--theme-app)] text-sm transition-all duration-200 box-border", "focus:outline-none focus:border-[var(--theme-element)] focus:shadow-[0_0_0_3px_color-mix(in_srgb,var(--theme-element)_25%,transparent)]", "placeholder:text-[var(--theme-muted-text-color)]", "disabled:bg-[var(--theme-muted-panel-bg)] disabled:text-[var(--theme-muted-text-color)] disabled:cursor-not-allowed disabled:opacity-70", mono && "font-mono text-[0.8125rem]", error && "border-[#ef4444]", isPassword && "pr-11", className);
266
+ return /* @__PURE__ */ jsxs("div", {
267
+ className: "flex flex-col gap-1.5",
268
+ children: [
269
+ label && /* @__PURE__ */ jsxs("label", {
270
+ className: "text-[0.8125rem] font-medium text-[var(--theme-app)]",
271
+ htmlFor: id,
272
+ children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
273
+ }),
274
+ /* @__PURE__ */ jsxs("div", {
275
+ className: isPassword ? "relative w-full" : "",
276
+ children: [/* @__PURE__ */ jsx("input", {
277
+ ref,
278
+ type: inputType,
279
+ className: inputClasses,
280
+ ...props,
281
+ id,
282
+ required,
283
+ "aria-invalid": error ? true : props["aria-invalid"],
284
+ "aria-describedby": describedBy
285
+ }), isPassword && /* @__PURE__ */ jsx("button", {
286
+ type: "button",
287
+ className: "absolute right-3 top-1/2 -translate-y-1/2 bg-transparent border-none text-[var(--theme-muted-text-color)] cursor-pointer p-1 transition-colors duration-200 flex items-center justify-center hover:text-[var(--theme-app)]",
288
+ onClick: () => setShowPassword(!showPassword),
289
+ tabIndex: -1,
290
+ "aria-label": showPassword ? "Hide password" : "Show password",
291
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, { className: "w-5 h-5" }) : /* @__PURE__ */ jsx(EyeIcon, { className: "w-5 h-5" })
292
+ })]
293
+ }),
294
+ error && /* @__PURE__ */ jsxs("span", {
295
+ id: errorId,
296
+ role: "alert",
297
+ className: "text-[0.8125rem] text-[#ef4444] flex items-center gap-1.5",
298
+ children: [/* @__PURE__ */ jsx(AlertIcon, { className: "w-4 h-4" }), error]
299
+ })
300
+ ]
379
301
  });
380
- }
302
+ });
303
+ Input.displayName = "Input";
304
+ var Textarea = forwardRef(({ label, error, className = "", mono, required, ...props }, ref) => {
305
+ const generatedId = useId();
306
+ const id = props.id ?? generatedId;
307
+ const errorId = `${id}-error`;
308
+ const describedBy = [props["aria-describedby"], error ? errorId : void 0].filter(Boolean).join(" ") || void 0;
309
+ const textareaClasses = cx("w-full py-3 px-4 bg-[var(--theme-muted-panel-bg)] border border-[var(--theme-border)] rounded-lg text-[var(--theme-app)] text-sm transition-all duration-200 box-border min-h-[140px] resize-y", "focus:outline-none focus:border-[var(--theme-element)] focus:shadow-[0_0_0_3px_color-mix(in_srgb,var(--theme-element)_25%,transparent)]", "placeholder:text-[var(--theme-muted-text-color)]", mono && "font-mono text-[0.8125rem]", error && "border-[#ef4444]", className);
310
+ return /* @__PURE__ */ jsxs("div", {
311
+ className: "flex flex-col gap-1.5",
312
+ children: [
313
+ label && /* @__PURE__ */ jsxs("label", {
314
+ className: "text-[0.8125rem] font-medium text-[var(--theme-app)]",
315
+ htmlFor: id,
316
+ children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})]
317
+ }),
318
+ /* @__PURE__ */ jsx("textarea", {
319
+ ref,
320
+ className: textareaClasses,
321
+ ...props,
322
+ id,
323
+ required,
324
+ "aria-invalid": error ? true : props["aria-invalid"],
325
+ "aria-describedby": describedBy
326
+ }),
327
+ error && /* @__PURE__ */ jsxs("span", {
328
+ id: errorId,
329
+ role: "alert",
330
+ className: "text-[0.8125rem] text-[#ef4444] flex items-center gap-1.5",
331
+ children: [/* @__PURE__ */ jsx(AlertIcon, { className: "w-4 h-4" }), error]
332
+ })
333
+ ]
334
+ });
335
+ });
336
+ Textarea.displayName = "Textarea";
337
+ //#endregion
338
+ //#region src/components/inputClasses.ts
339
+ var inputClasses = "w-full rounded-md border theme-border bg-(--theme-input-bg) px-3 py-2 text-sm text-(--theme-panel) outline-none focus:ring-2 focus:ring-(--theme-element)";
381
340
  //#endregion
382
341
  //#region src/components/LetterBadge.tsx
383
342
  /** A compact letter badge ("AI", "PUB", "SSH") used as a type mark in place of
@@ -389,79 +348,38 @@ function LetterBadge({ text, className }) {
389
348
  });
390
349
  }
391
350
  //#endregion
392
- //#region src/components/FilterPill.tsx
393
- function FilterPill({ active, onClick, children }) {
394
- return /* @__PURE__ */ jsx("button", {
395
- type: "button",
396
- onClick,
397
- className: cx("px-3 py-1.5 text-xs font-medium rounded-full border transition-all duration-200", active ? "bg-[var(--theme-panel-bg)] border-[var(--theme-border)] text-[var(--theme-panel)] shadow-sm" : "bg-transparent border-transparent text-[var(--theme-muted-text-color)] hover:text-[var(--theme-panel)] hover:bg-[var(--theme-hover)]"),
398
- children
399
- });
400
- }
401
- //#endregion
402
- //#region src/components/Filter.tsx
403
- function Filter({ filter, setFilter, placeholder = "Filter...", pills, activePill, onPillChange }) {
351
+ //#region src/components/Overview.tsx
352
+ function Overview({ Icon, name, type, children, right, className, loading, iconSize = 75 }) {
404
353
  return /* @__PURE__ */ jsxs("div", {
405
- className: "flex items-center gap-2",
406
- children: [/* @__PURE__ */ jsxs("div", {
407
- className: "relative flex items-center",
408
- children: [/* @__PURE__ */ jsx("input", {
409
- placeholder,
410
- type: "text",
411
- value: filter,
412
- onChange: (e) => setFilter(e.target.value.toLowerCase()),
413
- className: "shadow-sm focus:ring-1 block w-full pr-3 sm:text-sm theme-border rounded-md py-1.5 pl-8"
414
- }), /* @__PURE__ */ jsx("div", {
415
- className: "absolute inset-y-0 left-0 flex py-2.5 pl-2",
416
- children: /* @__PURE__ */ jsx(FilterIcon, {})
417
- })]
418
- }), pills && onPillChange && /* @__PURE__ */ jsx("div", {
419
- className: "flex gap-1",
420
- children: pills.map((pill) => /* @__PURE__ */ jsx("button", {
421
- type: "button",
422
- onClick: () => onPillChange(pill.value),
423
- className: cx("px-2.5 py-1 text-xs rounded-full transition-colors border", activePill === pill.value ? "bg-(--theme-element) text-(--theme-element-text) border-transparent" : "theme-border theme-text hover:theme-hover"),
424
- children: pill.label
425
- }, pill.value))
426
- })]
427
- });
428
- }
429
- //#endregion
430
- //#region src/components/Sort.tsx
431
- var SortIcon = ({ currentlySorted, currentDirection, name }) => {
432
- const style = { transform: "translateY(2px)" };
433
- if (currentlySorted === name) {
434
- if (currentDirection === "ascending") return /* @__PURE__ */ jsx(SortDownIcon, { style });
435
- else if (currentDirection === "descending") return /* @__PURE__ */ jsx(SortUpIcon, { style });
436
- } else return /* @__PURE__ */ jsx(SortUnsortedIcon, { style });
437
- return null;
438
- };
439
- function Sort({ currentlySorted, name, setCurrentDirection, setCurrentlySorted, currentDirection }) {
440
- const t = useStrings().common;
441
- const sort = (sorted, direction) => {
442
- if (sorted === name) {
443
- if (direction === "ascending") setCurrentDirection("descending");
444
- else if (direction === "descending") {
445
- setCurrentDirection("unsorted");
446
- setCurrentlySorted("");
447
- }
448
- } else {
449
- setCurrentDirection("ascending");
450
- setCurrentlySorted(name);
451
- }
452
- };
453
- return /* @__PURE__ */ jsx("div", {
454
- className: "inline",
455
- children: /* @__PURE__ */ jsx("button", {
456
- type: "button",
457
- "aria-label": t.sort,
458
- onClick: () => sort(currentlySorted, currentDirection),
459
- children: /* @__PURE__ */ jsx(SortIcon, {
460
- currentDirection,
461
- currentlySorted,
462
- name
354
+ className: cx("flex items-center w-full", className),
355
+ children: [
356
+ loading ? /* @__PURE__ */ jsx(Skeleton, {
357
+ width: iconSize,
358
+ height: iconSize,
359
+ circle: true
360
+ }) : Icon,
361
+ /* @__PURE__ */ jsx("div", {
362
+ className: "flex flex-col min-w-0 flex-1",
363
+ children: loading ? /* @__PURE__ */ jsx(Skeleton, {
364
+ count: 2,
365
+ height: 12
366
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
367
+ /* @__PURE__ */ jsx("div", {
368
+ className: "font-semibold text-xl tracking-tight",
369
+ children: name
370
+ }),
371
+ type && /* @__PURE__ */ jsx("p", { children: type }),
372
+ /* @__PURE__ */ jsx("div", {
373
+ className: "flex flex-row gap-x-2",
374
+ children
375
+ })
376
+ ] })
377
+ }),
378
+ right && /* @__PURE__ */ jsx("div", {
379
+ className: "shrink-0 self-center",
380
+ children: right
463
381
  })
464
- })
382
+ ]
465
383
  });
466
384
  }
467
385
  //#endregion
@@ -496,6 +414,93 @@ function Pagination({ endingNumber, endingTime, nextCallback, prevCallback, roun
496
414
  });
497
415
  }
498
416
  //#endregion
417
+ //#region src/components/relativeTime.ts
418
+ var TICK_MS = 3e4;
419
+ function compactRelativeMinutes(minutes, t) {
420
+ if (!Number.isFinite(minutes) || minutes < 1) return t.now;
421
+ if (minutes < 60) return t.minutes(minutes);
422
+ const hours = Math.floor(minutes / 60);
423
+ if (hours < 24) return t.hours(hours);
424
+ const days = Math.floor(hours / 24);
425
+ if (days < 365) return t.days(days);
426
+ return t.years(Math.floor(days / 365));
427
+ }
428
+ /** A clock that re-reads itself, so "5m" ages while the page sits open, with
429
+ * the abbreviated form a row has room for. */
430
+ function useRelativeTime() {
431
+ const t = useStrings().time;
432
+ const [now, setNow] = useState(() => DateTime.now());
433
+ useEffect(() => {
434
+ const id = setInterval(() => setNow(DateTime.now()), TICK_MS);
435
+ return () => clearInterval(id);
436
+ }, []);
437
+ const compactMinutes = (minutes) => compactRelativeMinutes(minutes, t);
438
+ return {
439
+ now,
440
+ compactMinutes,
441
+ compact: (iso) => iso ? compactMinutes(Math.floor(now.diff(DateTime.fromISO(iso)).as("minutes"))) : ""
442
+ };
443
+ }
444
+ //#endregion
445
+ //#region src/components/SettingModeOption.tsx
446
+ /** A labeled + described radio option row, used inside a SettingsCard to pick
447
+ * between modes (e.g. default vs custom). Stacked rows share the card's
448
+ * dividers via `isFirst`. */
449
+ function SettingModeOption({ name, checked, onSelect, label, description, isFirst, disabled }) {
450
+ return /* @__PURE__ */ jsxs("label", {
451
+ className: cx("flex items-start gap-3 px-5 py-4", disabled ? "cursor-default opacity-60" : "cursor-pointer", !isFirst && "border-t theme-border"),
452
+ children: [/* @__PURE__ */ jsx("input", {
453
+ type: "radio",
454
+ name,
455
+ checked,
456
+ onChange: onSelect,
457
+ disabled,
458
+ className: "mt-0.5 w-4 h-4 theme-input shrink-0"
459
+ }), /* @__PURE__ */ jsxs("div", {
460
+ className: "min-w-0",
461
+ children: [/* @__PURE__ */ jsx("div", {
462
+ className: "font-semibold theme-text leading-tight",
463
+ children: label
464
+ }), /* @__PURE__ */ jsx("div", {
465
+ className: "theme-muted-text text-xs leading-snug mt-0.5",
466
+ children: description
467
+ })]
468
+ })]
469
+ });
470
+ }
471
+ //#endregion
472
+ //#region src/components/SettingRow.tsx
473
+ function SettingRow({ label, htmlFor, helpText, children, isFirst, fill, onClick }) {
474
+ return /* @__PURE__ */ jsxs("div", {
475
+ role: "none",
476
+ onClick,
477
+ className: cx("px-5 py-5 grid grid-cols-1 gap-x-8 gap-y-2", fill ? "md:grid-cols-[200px_1fr]" : "md:grid-cols-[1fr_auto]", !isFirst && "border-t theme-border", onClick && "cursor-pointer select-none hover:theme-hover transition-colors"),
478
+ children: [/* @__PURE__ */ jsxs("div", {
479
+ className: "flex flex-col gap-y-1",
480
+ children: [/* @__PURE__ */ jsx("label", {
481
+ htmlFor,
482
+ className: "font-semibold theme-text leading-tight",
483
+ children: label
484
+ }), helpText && /* @__PURE__ */ jsx("div", {
485
+ className: "theme-muted-text text-xs leading-snug",
486
+ children: helpText
487
+ })]
488
+ }), /* @__PURE__ */ jsx("div", {
489
+ className: cx("min-w-0", !fill && "flex items-center justify-end"),
490
+ children
491
+ })]
492
+ });
493
+ }
494
+ function SettingSection({ title, isFirst }) {
495
+ return /* @__PURE__ */ jsx("div", {
496
+ className: cx("px-5 py-3 theme-muted-panel", !isFirst && "border-t theme-border"),
497
+ children: /* @__PURE__ */ jsx("h2", {
498
+ className: "font-semibold text-sm uppercase tracking-wider theme-muted-text",
499
+ children: title
500
+ })
501
+ });
502
+ }
503
+ //#endregion
499
504
  //#region src/components/SettingsCard.tsx
500
505
  /**
501
506
  * A settings card: an optional sentence-case section label sits above a
@@ -538,63 +543,59 @@ function SettingsGroup({ title, children, className }) {
538
543
  });
539
544
  }
540
545
  //#endregion
541
- //#region src/components/SettingRow.tsx
542
- function SettingRow({ label, htmlFor, helpText, children, isFirst, fill, onClick }) {
543
- return /* @__PURE__ */ jsxs("div", {
544
- role: "none",
545
- onClick,
546
- className: cx("px-5 py-5 grid grid-cols-1 gap-x-8 gap-y-2", fill ? "md:grid-cols-[200px_1fr]" : "md:grid-cols-[1fr_auto]", !isFirst && "border-t theme-border", onClick && "cursor-pointer select-none hover:theme-hover transition-colors"),
547
- children: [/* @__PURE__ */ jsxs("div", {
548
- className: "flex flex-col gap-y-1",
549
- children: [/* @__PURE__ */ jsx("label", {
550
- htmlFor,
551
- className: "font-semibold theme-text leading-tight",
552
- children: label
553
- }), helpText && /* @__PURE__ */ jsx("div", {
554
- className: "theme-muted-text text-xs leading-snug",
555
- children: helpText
556
- })]
557
- }), /* @__PURE__ */ jsx("div", {
558
- className: cx("min-w-0", !fill && "flex items-center justify-end"),
559
- children
560
- })]
561
- });
562
- }
563
- function SettingSection({ title, isFirst }) {
546
+ //#region src/components/Sort.tsx
547
+ var SortIcon = ({ currentlySorted, currentDirection, name }) => {
548
+ const style = { transform: "translateY(2px)" };
549
+ if (currentlySorted === name) {
550
+ if (currentDirection === "ascending") return /* @__PURE__ */ jsx(SortDownIcon, { style });
551
+ else if (currentDirection === "descending") return /* @__PURE__ */ jsx(SortUpIcon, { style });
552
+ } else return /* @__PURE__ */ jsx(SortUnsortedIcon, { style });
553
+ return null;
554
+ };
555
+ function Sort({ currentlySorted, name, setCurrentDirection, setCurrentlySorted, currentDirection }) {
556
+ const t = useStrings().common;
557
+ const sort = (sorted, direction) => {
558
+ if (sorted === name) {
559
+ if (direction === "ascending") setCurrentDirection("descending");
560
+ else if (direction === "descending") {
561
+ setCurrentDirection("unsorted");
562
+ setCurrentlySorted("");
563
+ }
564
+ } else {
565
+ setCurrentDirection("ascending");
566
+ setCurrentlySorted(name);
567
+ }
568
+ };
564
569
  return /* @__PURE__ */ jsx("div", {
565
- className: cx("px-5 py-3 theme-muted-panel", !isFirst && "border-t theme-border"),
566
- children: /* @__PURE__ */ jsx("h2", {
567
- className: "font-semibold text-sm uppercase tracking-wider theme-muted-text",
568
- children: title
570
+ className: "inline",
571
+ children: /* @__PURE__ */ jsx("button", {
572
+ type: "button",
573
+ "aria-label": t.sort,
574
+ onClick: () => sort(currentlySorted, currentDirection),
575
+ children: /* @__PURE__ */ jsx(SortIcon, {
576
+ currentDirection,
577
+ currentlySorted,
578
+ name
579
+ })
569
580
  })
570
581
  });
571
582
  }
572
583
  //#endregion
573
- //#region src/components/SettingModeOption.tsx
574
- /** A labeled + described radio option row, used inside a SettingsCard to pick
575
- * between modes (e.g. default vs custom). Stacked rows share the card's
576
- * dividers via `isFirst`. */
577
- function SettingModeOption({ name, checked, onSelect, label, description, isFirst, disabled }) {
584
+ //#region src/components/Toggle.tsx
585
+ function Toggle({ checked, onChange, disabled, label }) {
578
586
  return /* @__PURE__ */ jsxs("label", {
579
- className: cx("flex items-start gap-3 px-5 py-4", disabled ? "cursor-default opacity-60" : "cursor-pointer", !isFirst && "border-t theme-border"),
587
+ role: "none",
588
+ onClick: (e) => e.stopPropagation(),
589
+ className: cx("relative inline-flex items-center", disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
580
590
  children: [/* @__PURE__ */ jsx("input", {
581
- type: "radio",
582
- name,
591
+ type: "checkbox",
592
+ className: "peer sr-only",
583
593
  checked,
584
- onChange: onSelect,
594
+ onChange: (e) => onChange(e.target.checked),
585
595
  disabled,
586
- className: "mt-0.5 w-4 h-4 theme-input shrink-0"
587
- }), /* @__PURE__ */ jsxs("div", {
588
- className: "min-w-0",
589
- children: [/* @__PURE__ */ jsx("div", {
590
- className: "font-semibold theme-text leading-tight",
591
- children: label
592
- }), /* @__PURE__ */ jsx("div", {
593
- className: "theme-muted-text text-xs leading-snug mt-0.5",
594
- children: description
595
- })]
596
- })]
596
+ "aria-label": label
597
+ }), /* @__PURE__ */ jsx("div", { className: "h-6 w-11 rounded-full bg-(--theme-muted-panel-bg) transition-colors peer-checked:bg-(--theme-element) peer-focus-visible:ring-2 peer-focus-visible:ring-[color-mix(in_srgb,var(--theme-element)_25%,transparent)] after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white after:shadow-sm after:transition-transform after:content-[\"\"] motion-reduce:after:transition-none peer-checked:after:translate-x-5" })]
597
598
  });
598
599
  }
599
600
  //#endregion
600
- export { AddMenu, Avatar, BareModal, BreadcrumbProvider, BreadcrumbsActionButton, BreadcrumbsRightAction, BreadcrumbsTitleAction, Button, CREATE_MODAL_SELECT_FILTER_THRESHOLD, Callout, Card, CardHeader, CompactTable, ConfirmModal, ControlledCollapsiblePanel, CopyCodeBlock, CopyToClipboard, CreateModal, CreateModalAutocompleteField, CreateModalField, CreateModalSelect, CreateModalSwitch, CreateModalTextarea, CustomListbox, DEFAULT_PRESET, DescriptionList, DescriptionListItem, Dropdown, DropdownCaret, EmptyState, Filter, FilterPill, GlobalTooltip, HeaderActionMenu, HeaderAddButton, HeaderEditButton, HeaderRowMenu, IconButton, Indicator, Input, LetterBadge, Loader, Markdown, MaybeCollapsible, MethodButton, MultiSelectDropdown, Overview, PageHeader, Pagination, RequiredMark, SectionHeader, SettingModeOption, SettingRow, SettingSection, SettingsCard, SettingsGroup, Sort, StatusBadge, StatusDot, SwitchToggle, SwitchToggleSmall, THEME_PRESETS, THEME_TOKENS, TOOLTIP_ID, Table, Textarea, Toggle, TooltipInfo, UIProvider, UncontrolledCollapsiblePanel, applyTheme, avatarInitials, contrastRatio, dangerButtonClasses, deriveTheme, filterOption, flattenOptions, formatTooltipContent, ghostButtonClasses, headerActionButtonClasses, inputClasses, isCategory, isDarkColor, isOption, modalPanelClasses, primaryButtonClasses, statusTextClass, toggleSetMember, truncationTooltipProps, useBreadcrumb, useClearBreadcrumbs, useCssIsDark, useHideBreadcrumbs, useNavigation, useNotify, usePersistedState, useProvisionCorsRules, useRelativeTime, useRepoSuggestions, useRunFile, useSetBreadcrumbs, useSetDefaultBreadcrumbs, useSlots, useStrings, useWorkflowJsonResolver };
601
+ export { AddMenu, Avatar, BareModal, BreadcrumbProvider, BreadcrumbsActionButton, BreadcrumbsRightAction, BreadcrumbsTitleAction, Button, CREATE_MODAL_SELECT_FILTER_THRESHOLD, Callout, Card, CardHeader, CompactTable, ConfirmModal, ControlledCollapsiblePanel, CopyCodeBlock, CopyToClipboard, CreateModal, CreateModalAutocompleteField, CreateModalField, CreateModalSelect, CreateModalSwitch, CreateModalTextarea, CustomListbox, DEFAULT_PRESET, DescriptionList, DescriptionListItem, Dropdown, DropdownCaret, EmptyState, Filter, FilterPill, GlobalTooltip, HeaderActionMenu, HeaderAddButton, HeaderEditButton, HeaderRowMenu, IconButton, Indicator, Input, LetterBadge, Loader, Markdown, MaybeCollapsible, MethodButton, MultiSelectDropdown, Overview, PageHeader, Pagination, RequiredMark, SectionHeader, SettingModeOption, SettingRow, SettingSection, SettingsCard, SettingsGroup, Sort, StatusBadge, StatusDot, SwitchToggle, SwitchToggleSmall, THEME_PRESETS, THEME_TOKENS, TOOLTIP_ID, Table, Textarea, Toggle, TooltipInfo, UIProvider, UncontrolledCollapsiblePanel, applyTheme, avatarInitials, contrastRatio, dangerButtonClasses, deriveTheme, filterOption, flattenOptions, formatTooltipContent, ghostButtonClasses, headerActionButtonClasses, inputClasses, isCategory, isDarkColor, isOption, modalPanelClasses, primaryButtonClasses, statusTextClass, toggleSetMember, truncationTooltipProps, useBreadcrumb, useClearBreadcrumbs, useCssIsDark, useHideBreadcrumbs, useNavigation, useNotify, useOptionalWorkflowEngine, usePersistedState, useProvisionCorsRules, useRelativeTime, useRunFile, useSetBreadcrumbs, useSetDefaultBreadcrumbs, useSlots, useStrings, useWorkflowEngine, useWorkflowEngineLoader };