@open-mercato/ui 0.4.2-canary-c02407ff85

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 (319) hide show
  1. package/build.mjs +62 -0
  2. package/dist/backend/AppShell.js +902 -0
  3. package/dist/backend/AppShell.js.map +7 -0
  4. package/dist/backend/ConfirmDialog.js +17 -0
  5. package/dist/backend/ConfirmDialog.js.map +7 -0
  6. package/dist/backend/ContextHelp.js +31 -0
  7. package/dist/backend/ContextHelp.js.map +7 -0
  8. package/dist/backend/CrudForm.js +2028 -0
  9. package/dist/backend/CrudForm.js.map +7 -0
  10. package/dist/backend/DataTable.js +1363 -0
  11. package/dist/backend/DataTable.js.map +7 -0
  12. package/dist/backend/EmptyState.js +52 -0
  13. package/dist/backend/EmptyState.js.map +7 -0
  14. package/dist/backend/FilterBar.js +140 -0
  15. package/dist/backend/FilterBar.js.map +7 -0
  16. package/dist/backend/FilterOverlay.js +279 -0
  17. package/dist/backend/FilterOverlay.js.map +7 -0
  18. package/dist/backend/FlashMessages.js +66 -0
  19. package/dist/backend/FlashMessages.js.map +7 -0
  20. package/dist/backend/JsonBuilder.js +322 -0
  21. package/dist/backend/JsonBuilder.js.map +7 -0
  22. package/dist/backend/JsonDisplay.js +203 -0
  23. package/dist/backend/JsonDisplay.js.map +7 -0
  24. package/dist/backend/Page.js +27 -0
  25. package/dist/backend/Page.js.map +7 -0
  26. package/dist/backend/PerspectiveSidebar.js +282 -0
  27. package/dist/backend/PerspectiveSidebar.js.map +7 -0
  28. package/dist/backend/RowActions.js +148 -0
  29. package/dist/backend/RowActions.js.map +7 -0
  30. package/dist/backend/TruncatedCell.js +92 -0
  31. package/dist/backend/TruncatedCell.js.map +7 -0
  32. package/dist/backend/UserMenu.js +107 -0
  33. package/dist/backend/UserMenu.js.map +7 -0
  34. package/dist/backend/ValueIcons.js +34 -0
  35. package/dist/backend/ValueIcons.js.map +7 -0
  36. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +1264 -0
  37. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +7 -0
  38. package/dist/backend/custom-fields/FieldDefinitionsManager.js +332 -0
  39. package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +7 -0
  40. package/dist/backend/dashboard/DashboardScreen.js +578 -0
  41. package/dist/backend/dashboard/DashboardScreen.js.map +7 -0
  42. package/dist/backend/dashboard/index.js +5 -0
  43. package/dist/backend/dashboard/index.js.map +7 -0
  44. package/dist/backend/dashboard/widgetRegistry.js +55 -0
  45. package/dist/backend/dashboard/widgetRegistry.js.map +7 -0
  46. package/dist/backend/detail/ActivitiesSection.js +962 -0
  47. package/dist/backend/detail/ActivitiesSection.js.map +7 -0
  48. package/dist/backend/detail/AddressEditor.js +413 -0
  49. package/dist/backend/detail/AddressEditor.js.map +7 -0
  50. package/dist/backend/detail/AddressTiles.js +437 -0
  51. package/dist/backend/detail/AddressTiles.js.map +7 -0
  52. package/dist/backend/detail/AddressesSection.js +264 -0
  53. package/dist/backend/detail/AddressesSection.js.map +7 -0
  54. package/dist/backend/detail/AttachmentDeleteDialog.js +41 -0
  55. package/dist/backend/detail/AttachmentDeleteDialog.js.map +7 -0
  56. package/dist/backend/detail/AttachmentMetadataDialog.js +517 -0
  57. package/dist/backend/detail/AttachmentMetadataDialog.js.map +7 -0
  58. package/dist/backend/detail/AttachmentsSection.js +367 -0
  59. package/dist/backend/detail/AttachmentsSection.js.map +7 -0
  60. package/dist/backend/detail/CustomDataSection.js +433 -0
  61. package/dist/backend/detail/CustomDataSection.js.map +7 -0
  62. package/dist/backend/detail/DetailFieldsSection.js +75 -0
  63. package/dist/backend/detail/DetailFieldsSection.js.map +7 -0
  64. package/dist/backend/detail/ErrorMessage.js +28 -0
  65. package/dist/backend/detail/ErrorMessage.js.map +7 -0
  66. package/dist/backend/detail/InlineEditors.js +681 -0
  67. package/dist/backend/detail/InlineEditors.js.map +7 -0
  68. package/dist/backend/detail/LoadingMessage.js +14 -0
  69. package/dist/backend/detail/LoadingMessage.js.map +7 -0
  70. package/dist/backend/detail/NotesSection.js +1032 -0
  71. package/dist/backend/detail/NotesSection.js.map +7 -0
  72. package/dist/backend/detail/TabEmptyState.js +25 -0
  73. package/dist/backend/detail/TabEmptyState.js.map +7 -0
  74. package/dist/backend/detail/TagsSection.js +254 -0
  75. package/dist/backend/detail/TagsSection.js.map +7 -0
  76. package/dist/backend/detail/addressFormat.js +77 -0
  77. package/dist/backend/detail/addressFormat.js.map +7 -0
  78. package/dist/backend/detail/index.js +34 -0
  79. package/dist/backend/detail/index.js.map +7 -0
  80. package/dist/backend/fields/registry.generated.js +8 -0
  81. package/dist/backend/fields/registry.generated.js.map +7 -0
  82. package/dist/backend/fields/registry.js +29 -0
  83. package/dist/backend/fields/registry.js.map +7 -0
  84. package/dist/backend/indexes/PartialIndexBanner.js +58 -0
  85. package/dist/backend/indexes/PartialIndexBanner.js.map +7 -0
  86. package/dist/backend/indexes/store.js +62 -0
  87. package/dist/backend/indexes/store.js.map +7 -0
  88. package/dist/backend/injection/InjectionSpot.js +179 -0
  89. package/dist/backend/injection/InjectionSpot.js.map +7 -0
  90. package/dist/backend/injection/PageInjectionBoundary.js +26 -0
  91. package/dist/backend/injection/PageInjectionBoundary.js.map +7 -0
  92. package/dist/backend/injection/helpers.js +26 -0
  93. package/dist/backend/injection/helpers.js.map +7 -0
  94. package/dist/backend/injection/widgetRegistry.js +55 -0
  95. package/dist/backend/injection/widgetRegistry.js.map +7 -0
  96. package/dist/backend/inputs/ComboboxInput.js +225 -0
  97. package/dist/backend/inputs/ComboboxInput.js.map +7 -0
  98. package/dist/backend/inputs/LookupSelect.js +191 -0
  99. package/dist/backend/inputs/LookupSelect.js.map +7 -0
  100. package/dist/backend/inputs/PhoneNumberField.js +100 -0
  101. package/dist/backend/inputs/PhoneNumberField.js.map +7 -0
  102. package/dist/backend/inputs/SwitchableMarkdownInput.js +92 -0
  103. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +7 -0
  104. package/dist/backend/inputs/TagsInput.js +222 -0
  105. package/dist/backend/inputs/TagsInput.js.map +7 -0
  106. package/dist/backend/inputs/index.js +6 -0
  107. package/dist/backend/inputs/index.js.map +7 -0
  108. package/dist/backend/operations/LastOperationBanner.js +80 -0
  109. package/dist/backend/operations/LastOperationBanner.js.map +7 -0
  110. package/dist/backend/operations/store.js +183 -0
  111. package/dist/backend/operations/store.js.map +7 -0
  112. package/dist/backend/schedule/ScheduleAgenda.js +107 -0
  113. package/dist/backend/schedule/ScheduleAgenda.js.map +7 -0
  114. package/dist/backend/schedule/ScheduleGrid.js +107 -0
  115. package/dist/backend/schedule/ScheduleGrid.js.map +7 -0
  116. package/dist/backend/schedule/ScheduleToolbar.js +166 -0
  117. package/dist/backend/schedule/ScheduleToolbar.js.map +7 -0
  118. package/dist/backend/schedule/ScheduleView.js +165 -0
  119. package/dist/backend/schedule/ScheduleView.js.map +7 -0
  120. package/dist/backend/schedule/index.js +6 -0
  121. package/dist/backend/schedule/index.js.map +7 -0
  122. package/dist/backend/schedule/recurrence.js +83 -0
  123. package/dist/backend/schedule/recurrence.js.map +7 -0
  124. package/dist/backend/schedule/types.js +1 -0
  125. package/dist/backend/schedule/types.js.map +7 -0
  126. package/dist/backend/upgrades/UpgradeActionBanner.js +91 -0
  127. package/dist/backend/upgrades/UpgradeActionBanner.js.map +7 -0
  128. package/dist/backend/utils/api.js +127 -0
  129. package/dist/backend/utils/api.js.map +7 -0
  130. package/dist/backend/utils/apiCall.js +48 -0
  131. package/dist/backend/utils/apiCall.js.map +7 -0
  132. package/dist/backend/utils/crud.js +126 -0
  133. package/dist/backend/utils/crud.js.map +7 -0
  134. package/dist/backend/utils/customFieldColumns.js +56 -0
  135. package/dist/backend/utils/customFieldColumns.js.map +7 -0
  136. package/dist/backend/utils/customFieldDefs.js +143 -0
  137. package/dist/backend/utils/customFieldDefs.js.map +7 -0
  138. package/dist/backend/utils/customFieldFilters.js +126 -0
  139. package/dist/backend/utils/customFieldFilters.js.map +7 -0
  140. package/dist/backend/utils/customFieldForms.js +162 -0
  141. package/dist/backend/utils/customFieldForms.js.map +7 -0
  142. package/dist/backend/utils/customFieldValues.js +26 -0
  143. package/dist/backend/utils/customFieldValues.js.map +7 -0
  144. package/dist/backend/utils/flash.js +16 -0
  145. package/dist/backend/utils/flash.js.map +7 -0
  146. package/dist/backend/utils/nav.js +185 -0
  147. package/dist/backend/utils/nav.js.map +7 -0
  148. package/dist/backend/utils/serverErrors.js +230 -0
  149. package/dist/backend/utils/serverErrors.js.map +7 -0
  150. package/dist/frontend/AuthFooter.js +23 -0
  151. package/dist/frontend/AuthFooter.js.map +7 -0
  152. package/dist/frontend/LanguageSwitcher.js +57 -0
  153. package/dist/frontend/LanguageSwitcher.js.map +7 -0
  154. package/dist/frontend/Layout.js +14 -0
  155. package/dist/frontend/Layout.js.map +7 -0
  156. package/dist/index.js +32 -0
  157. package/dist/index.js.map +7 -0
  158. package/dist/primitives/DataLoader.js +67 -0
  159. package/dist/primitives/DataLoader.js.map +7 -0
  160. package/dist/primitives/ErrorNotice.js +20 -0
  161. package/dist/primitives/ErrorNotice.js.map +7 -0
  162. package/dist/primitives/alert.js +38 -0
  163. package/dist/primitives/alert.js.map +7 -0
  164. package/dist/primitives/badge.js +28 -0
  165. package/dist/primitives/badge.js.map +7 -0
  166. package/dist/primitives/button.js +44 -0
  167. package/dist/primitives/button.js.map +7 -0
  168. package/dist/primitives/card.js +91 -0
  169. package/dist/primitives/card.js.map +7 -0
  170. package/dist/primitives/checkbox.js +28 -0
  171. package/dist/primitives/checkbox.js.map +7 -0
  172. package/dist/primitives/dialog.js +90 -0
  173. package/dist/primitives/dialog.js.map +7 -0
  174. package/dist/primitives/input.js +22 -0
  175. package/dist/primitives/input.js.map +7 -0
  176. package/dist/primitives/label.js +21 -0
  177. package/dist/primitives/label.js.map +7 -0
  178. package/dist/primitives/separator.js +9 -0
  179. package/dist/primitives/separator.js.map +7 -0
  180. package/dist/primitives/spinner.js +24 -0
  181. package/dist/primitives/spinner.js.map +7 -0
  182. package/dist/primitives/switch.js +80 -0
  183. package/dist/primitives/switch.js.map +7 -0
  184. package/dist/primitives/table.js +29 -0
  185. package/dist/primitives/table.js.map +7 -0
  186. package/dist/primitives/tabs.js +87 -0
  187. package/dist/primitives/tabs.js.map +7 -0
  188. package/dist/primitives/textarea.js +21 -0
  189. package/dist/primitives/textarea.js.map +7 -0
  190. package/dist/primitives/tooltip.js +60 -0
  191. package/dist/primitives/tooltip.js.map +7 -0
  192. package/dist/theme/QueryProvider.js +44 -0
  193. package/dist/theme/QueryProvider.js.map +7 -0
  194. package/dist/theme/ThemeProvider.js +95 -0
  195. package/dist/theme/ThemeProvider.js.map +7 -0
  196. package/dist/theme/ThemeToggle.js +88 -0
  197. package/dist/theme/ThemeToggle.js.map +7 -0
  198. package/dist/theme/index.js +10 -0
  199. package/dist/theme/index.js.map +7 -0
  200. package/dist/types/react-big-calendar.d.js +1 -0
  201. package/dist/types/react-big-calendar.d.js.map +7 -0
  202. package/jest.config.cjs +23 -0
  203. package/jest.setup.ts +55 -0
  204. package/package.json +105 -0
  205. package/src/backend/AppShell.tsx +1096 -0
  206. package/src/backend/ConfirmDialog.tsx +19 -0
  207. package/src/backend/ContextHelp.tsx +38 -0
  208. package/src/backend/CrudForm.tsx +2503 -0
  209. package/src/backend/DataTable.tsx +1730 -0
  210. package/src/backend/EmptyState.tsx +65 -0
  211. package/src/backend/FilterBar.tsx +161 -0
  212. package/src/backend/FilterOverlay.tsx +328 -0
  213. package/src/backend/FlashMessages.tsx +82 -0
  214. package/src/backend/JsonBuilder.tsx +362 -0
  215. package/src/backend/JsonDisplay.tsx +254 -0
  216. package/src/backend/Page.tsx +30 -0
  217. package/src/backend/PerspectiveSidebar.tsx +337 -0
  218. package/src/backend/RowActions.tsx +151 -0
  219. package/src/backend/TruncatedCell.tsx +133 -0
  220. package/src/backend/UserMenu.tsx +118 -0
  221. package/src/backend/ValueIcons.tsx +48 -0
  222. package/src/backend/__tests__/AppShell.test.tsx +115 -0
  223. package/src/backend/__tests__/CrudForm.render.test.tsx +30 -0
  224. package/src/backend/__tests__/DataTable.render.test.tsx +48 -0
  225. package/src/backend/__tests__/custom-field-filters.test.ts +72 -0
  226. package/src/backend/__tests__/custom-field-forms.test.ts +54 -0
  227. package/src/backend/__tests__/serverErrors.test.ts +83 -0
  228. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +1292 -0
  229. package/src/backend/custom-fields/FieldDefinitionsManager.tsx +381 -0
  230. package/src/backend/dashboard/DashboardScreen.tsx +684 -0
  231. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +112 -0
  232. package/src/backend/dashboard/index.ts +1 -0
  233. package/src/backend/dashboard/widgetRegistry.ts +68 -0
  234. package/src/backend/detail/ActivitiesSection.tsx +1284 -0
  235. package/src/backend/detail/AddressEditor.tsx +472 -0
  236. package/src/backend/detail/AddressTiles.tsx +587 -0
  237. package/src/backend/detail/AddressesSection.tsx +346 -0
  238. package/src/backend/detail/AttachmentDeleteDialog.tsx +56 -0
  239. package/src/backend/detail/AttachmentMetadataDialog.tsx +672 -0
  240. package/src/backend/detail/AttachmentsSection.tsx +414 -0
  241. package/src/backend/detail/CustomDataSection.tsx +530 -0
  242. package/src/backend/detail/DetailFieldsSection.tsx +147 -0
  243. package/src/backend/detail/ErrorMessage.tsx +32 -0
  244. package/src/backend/detail/InlineEditors.tsx +877 -0
  245. package/src/backend/detail/LoadingMessage.tsx +14 -0
  246. package/src/backend/detail/NotesSection.tsx +1275 -0
  247. package/src/backend/detail/TabEmptyState.tsx +48 -0
  248. package/src/backend/detail/TagsSection.tsx +314 -0
  249. package/src/backend/detail/addressFormat.tsx +121 -0
  250. package/src/backend/detail/index.ts +44 -0
  251. package/src/backend/fields/registry.generated.ts +8 -0
  252. package/src/backend/fields/registry.ts +38 -0
  253. package/src/backend/indexes/PartialIndexBanner.tsx +68 -0
  254. package/src/backend/indexes/store.ts +88 -0
  255. package/src/backend/injection/InjectionSpot.tsx +236 -0
  256. package/src/backend/injection/PageInjectionBoundary.tsx +31 -0
  257. package/src/backend/injection/helpers.ts +35 -0
  258. package/src/backend/injection/widgetRegistry.ts +68 -0
  259. package/src/backend/inputs/ComboboxInput.tsx +269 -0
  260. package/src/backend/inputs/LookupSelect.tsx +247 -0
  261. package/src/backend/inputs/PhoneNumberField.tsx +129 -0
  262. package/src/backend/inputs/SwitchableMarkdownInput.tsx +128 -0
  263. package/src/backend/inputs/TagsInput.tsx +259 -0
  264. package/src/backend/inputs/index.ts +5 -0
  265. package/src/backend/operations/LastOperationBanner.tsx +85 -0
  266. package/src/backend/operations/__tests__/LastOperationBanner.test.tsx +99 -0
  267. package/src/backend/operations/store.ts +230 -0
  268. package/src/backend/schedule/ScheduleAgenda.tsx +136 -0
  269. package/src/backend/schedule/ScheduleGrid.tsx +136 -0
  270. package/src/backend/schedule/ScheduleToolbar.tsx +178 -0
  271. package/src/backend/schedule/ScheduleView.tsx +198 -0
  272. package/src/backend/schedule/index.ts +5 -0
  273. package/src/backend/schedule/recurrence.ts +99 -0
  274. package/src/backend/schedule/types.ts +26 -0
  275. package/src/backend/upgrades/UpgradeActionBanner.tsx +128 -0
  276. package/src/backend/utils/__tests__/apiCall.test.ts +109 -0
  277. package/src/backend/utils/__tests__/crud.test.ts +87 -0
  278. package/src/backend/utils/__tests__/customFieldDefs.test.ts +25 -0
  279. package/src/backend/utils/__tests__/customFieldValues.test.ts +35 -0
  280. package/src/backend/utils/api.ts +149 -0
  281. package/src/backend/utils/apiCall.ts +96 -0
  282. package/src/backend/utils/crud.ts +174 -0
  283. package/src/backend/utils/customFieldColumns.ts +71 -0
  284. package/src/backend/utils/customFieldDefs.ts +245 -0
  285. package/src/backend/utils/customFieldFilters.ts +145 -0
  286. package/src/backend/utils/customFieldForms.ts +196 -0
  287. package/src/backend/utils/customFieldValues.ts +41 -0
  288. package/src/backend/utils/flash.ts +17 -0
  289. package/src/backend/utils/nav.ts +238 -0
  290. package/src/backend/utils/serverErrors.ts +302 -0
  291. package/src/frontend/AuthFooter.tsx +29 -0
  292. package/src/frontend/LanguageSwitcher.tsx +66 -0
  293. package/src/frontend/Layout.tsx +13 -0
  294. package/src/index.ts +32 -0
  295. package/src/primitives/DataLoader.tsx +92 -0
  296. package/src/primitives/ErrorNotice.tsx +26 -0
  297. package/src/primitives/alert.tsx +52 -0
  298. package/src/primitives/badge.tsx +31 -0
  299. package/src/primitives/button.tsx +47 -0
  300. package/src/primitives/card.tsx +92 -0
  301. package/src/primitives/checkbox.tsx +28 -0
  302. package/src/primitives/dialog.tsx +110 -0
  303. package/src/primitives/input.tsx +20 -0
  304. package/src/primitives/label.tsx +18 -0
  305. package/src/primitives/separator.tsx +7 -0
  306. package/src/primitives/spinner.tsx +27 -0
  307. package/src/primitives/switch.tsx +86 -0
  308. package/src/primitives/table.tsx +27 -0
  309. package/src/primitives/tabs.tsx +128 -0
  310. package/src/primitives/textarea.tsx +20 -0
  311. package/src/primitives/tooltip.tsx +85 -0
  312. package/src/theme/QueryProvider.tsx +46 -0
  313. package/src/theme/ThemeProvider.tsx +120 -0
  314. package/src/theme/ThemeToggle.tsx +88 -0
  315. package/src/theme/index.ts +3 -0
  316. package/src/types/react-big-calendar.d.ts +16 -0
  317. package/tsconfig.build.json +11 -0
  318. package/tsconfig.json +9 -0
  319. package/watch.mjs +6 -0
@@ -0,0 +1,681 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import dynamic from "next/dynamic";
5
+ import { FileCode, Loader2, Mail, Pencil, Phone, X } from "lucide-react";
6
+ import { PhoneNumberField } from "@open-mercato/ui/backend/inputs/PhoneNumberField";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
8
+ import { Textarea } from "@open-mercato/ui/primitives/textarea";
9
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
10
+ import { cn } from "@open-mercato/shared/lib/utils";
11
+ import { LoadingMessage } from "./LoadingMessage.js";
12
+ import { mapCrudServerErrorToFormErrors } from "../utils/serverErrors.js";
13
+ function resolveInlineErrorMessage(err, fallbackMessage) {
14
+ const { message, fieldErrors } = mapCrudServerErrorToFormErrors(err);
15
+ const firstFieldError = fieldErrors ? Object.values(fieldErrors).find((text) => typeof text === "string" && text.trim().length) : null;
16
+ if (typeof firstFieldError === "string" && firstFieldError.trim().length) {
17
+ return firstFieldError.trim();
18
+ }
19
+ if (typeof message === "string" && message.trim().length) {
20
+ return message.trim();
21
+ }
22
+ if (err instanceof Error && typeof err.message === "string" && err.message.trim().length) {
23
+ return err.message.trim();
24
+ }
25
+ if (typeof err === "string" && err.trim().length) {
26
+ return err.trim();
27
+ }
28
+ return fallbackMessage;
29
+ }
30
+ function InlineTextEditor({
31
+ label,
32
+ value,
33
+ placeholder,
34
+ emptyLabel,
35
+ onSave,
36
+ type = "text",
37
+ inputType,
38
+ validator,
39
+ variant = "default",
40
+ activateOnClick = false,
41
+ containerClassName,
42
+ triggerClassName,
43
+ hideLabel = false,
44
+ renderDisplay,
45
+ onEditingChange,
46
+ renderActions,
47
+ saveLabel,
48
+ onDraftChange,
49
+ renderBelowInput
50
+ }) {
51
+ const t = useT();
52
+ const [editing, setEditing] = React.useState(false);
53
+ const [draft, setDraft] = React.useState(value ?? "");
54
+ const [error, setError] = React.useState(null);
55
+ const [saving, setSaving] = React.useState(false);
56
+ const computedSaveLabel = saveLabel ?? t("ui.detail.inline.saveShortcut", "Save \u2318\u23CE / Ctrl+Enter");
57
+ const fallbackError = React.useMemo(
58
+ () => t("ui.detail.inline.error", "Failed to save value."),
59
+ [t]
60
+ );
61
+ const resolvedType = React.useMemo(() => {
62
+ if (type && typeof type === "string") return type;
63
+ if (inputType && typeof inputType === "string") {
64
+ const normalized = inputType.toLowerCase();
65
+ if (normalized === "email" || normalized === "tel" || normalized === "url") {
66
+ return normalized;
67
+ }
68
+ }
69
+ return "text";
70
+ }, [inputType, type]);
71
+ React.useEffect(() => {
72
+ if (!editing) setDraft(value ?? "");
73
+ }, [editing, value]);
74
+ React.useEffect(() => {
75
+ if (onDraftChange) onDraftChange(draft);
76
+ }, [draft, onDraftChange]);
77
+ const containerClasses = cn(
78
+ "group overflow-hidden",
79
+ variant === "muted" ? "relative rounded border bg-muted/20 p-3" : variant === "plain" ? "relative flex items-center gap-3 rounded-none border-0 p-0" : "rounded-lg border p-4",
80
+ activateOnClick && !editing ? "cursor-pointer" : null,
81
+ containerClassName ?? null
82
+ );
83
+ const readOnlyWrapperClasses = cn(
84
+ "flex-1 min-w-0",
85
+ activateOnClick && !editing ? "cursor-pointer" : null,
86
+ variant === "plain" ? "flex items-center gap-2" : null
87
+ );
88
+ const triggerClasses = cn(
89
+ "shrink-0 transition-opacity duration-150",
90
+ editing ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100",
91
+ variant === "muted" ? "h-8 w-8" : null,
92
+ triggerClassName ?? null
93
+ );
94
+ const triggerSize = variant === "plain" ? "icon" : "sm";
95
+ const setEditingSafe = React.useCallback(
96
+ (next) => {
97
+ setEditing(next);
98
+ if (onEditingChange) onEditingChange(next);
99
+ },
100
+ [onEditingChange]
101
+ );
102
+ const handleActivate = React.useCallback(() => {
103
+ if (!editing) setEditingSafe(true);
104
+ }, [editing, setEditingSafe]);
105
+ const handleInteractiveClick = React.useCallback(
106
+ (event) => {
107
+ if (!activateOnClick || editing) return;
108
+ const target = event.target;
109
+ const interactiveElement = target.closest('button, input, select, textarea, a, [role="link"]');
110
+ if (interactiveElement) {
111
+ if (interactiveElement.tagName.toLowerCase() === "a") {
112
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
113
+ return;
114
+ }
115
+ event.preventDefault();
116
+ } else {
117
+ return;
118
+ }
119
+ }
120
+ handleActivate();
121
+ },
122
+ [activateOnClick, editing, handleActivate]
123
+ );
124
+ const handleContainerKeyDown = React.useCallback(
125
+ (event) => {
126
+ if (!activateOnClick || editing) return;
127
+ if (event.key === "Enter" || event.key === " ") {
128
+ event.preventDefault();
129
+ handleActivate();
130
+ }
131
+ },
132
+ [activateOnClick, editing, handleActivate]
133
+ );
134
+ const handleSave = React.useCallback(async () => {
135
+ const trimmed = draft.trim();
136
+ if (validator) {
137
+ const validationError = validator(trimmed);
138
+ if (validationError) {
139
+ setError(validationError);
140
+ return;
141
+ }
142
+ }
143
+ setError(null);
144
+ setSaving(true);
145
+ try {
146
+ await onSave(trimmed.length ? trimmed : null);
147
+ setEditingSafe(false);
148
+ } catch (err) {
149
+ setError(resolveInlineErrorMessage(err, fallbackError));
150
+ } finally {
151
+ setSaving(false);
152
+ }
153
+ }, [draft, fallbackError, onSave, setEditingSafe, validator]);
154
+ const interactiveProps = activateOnClick && !editing ? {
155
+ role: "button",
156
+ tabIndex: 0,
157
+ onKeyDown: handleContainerKeyDown
158
+ } : {};
159
+ const displayContent = React.useMemo(() => {
160
+ if (renderDisplay) {
161
+ return renderDisplay({ value, emptyLabel, type: resolvedType });
162
+ }
163
+ const baseValue = value && typeof value === "string" ? value : "";
164
+ const anchorClass = variant === "plain" ? "inline-flex max-w-full min-w-0 items-center gap-2 text-xl font-semibold leading-tight text-primary hover:text-primary/90 hover:underline" : "flex max-w-full min-w-0 items-center gap-2 text-sm text-primary hover:text-primary/90 hover:underline";
165
+ const textClass = variant === "plain" ? "text-2xl font-semibold leading-tight" : "text-sm break-words";
166
+ if (resolvedType === "email") {
167
+ if (!baseValue.length) {
168
+ return /* @__PURE__ */ jsx("p", { className: variant === "plain" ? "text-base text-muted-foreground" : "text-sm text-muted-foreground", children: emptyLabel });
169
+ }
170
+ return /* @__PURE__ */ jsxs("a", { className: anchorClass, href: `mailto:${baseValue}`, children: [
171
+ /* @__PURE__ */ jsx(Mail, { "aria-hidden": true, className: variant === "plain" ? "h-5 w-5" : "h-4 w-4" }),
172
+ /* @__PURE__ */ jsx("span", { className: "truncate min-w-0", children: baseValue })
173
+ ] });
174
+ }
175
+ if (!baseValue.length) {
176
+ return /* @__PURE__ */ jsx("p", { className: variant === "plain" ? "text-base text-muted-foreground" : "text-sm text-muted-foreground", children: emptyLabel });
177
+ }
178
+ if (resolvedType === "tel") {
179
+ const sanitizedValue = baseValue.replace(/[^+\d]/g, "");
180
+ const hrefValue = sanitizedValue.length ? sanitizedValue : baseValue;
181
+ return /* @__PURE__ */ jsxs("a", { className: anchorClass, href: `tel:${hrefValue}`, children: [
182
+ /* @__PURE__ */ jsx(Phone, { "aria-hidden": true, className: variant === "plain" ? "h-5 w-5" : "h-4 w-4" }),
183
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: baseValue })
184
+ ] });
185
+ }
186
+ if (resolvedType === "url") {
187
+ return /* @__PURE__ */ jsx("a", { className: textClass, href: baseValue, target: "_blank", rel: "noreferrer", children: baseValue });
188
+ }
189
+ return /* @__PURE__ */ jsx("p", { className: textClass, children: baseValue });
190
+ }, [emptyLabel, renderDisplay, resolvedType, value, variant]);
191
+ const editingContainerClass = variant === "plain" ? "mt-0 w-full max-w-sm space-y-3" : "mt-2 space-y-3";
192
+ return /* @__PURE__ */ jsx("div", { className: containerClasses, onClick: handleInteractiveClick, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2 min-w-0", children: [
193
+ /* @__PURE__ */ jsxs("div", { className: readOnlyWrapperClasses, ...interactiveProps, children: [
194
+ hideLabel ? null : /* @__PURE__ */ jsx("p", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }),
195
+ editing ? /* @__PURE__ */ jsxs(
196
+ "form",
197
+ {
198
+ className: editingContainerClass,
199
+ onSubmit: (event) => {
200
+ event.preventDefault();
201
+ if (!saving) void handleSave();
202
+ },
203
+ onKeyDown: (event) => {
204
+ if (event.key === "Escape") {
205
+ event.preventDefault();
206
+ setEditingSafe(false);
207
+ setError(null);
208
+ return;
209
+ }
210
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
211
+ event.preventDefault();
212
+ if (!saving) void handleSave();
213
+ }
214
+ },
215
+ children: [
216
+ resolvedType === "tel" ? /* @__PURE__ */ jsx(
217
+ PhoneNumberField,
218
+ {
219
+ value: draft.length ? draft : void 0,
220
+ onValueChange: (next) => {
221
+ if (error) setError(null);
222
+ setDraft(next ?? "");
223
+ },
224
+ placeholder,
225
+ autoFocus: true,
226
+ disabled: saving,
227
+ minDigits: 7
228
+ }
229
+ ) : /* @__PURE__ */ jsx(
230
+ "input",
231
+ {
232
+ className: "w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
233
+ value: draft,
234
+ onChange: (event) => {
235
+ if (error) setError(null);
236
+ setDraft(event.target.value);
237
+ },
238
+ placeholder,
239
+ type: inputType ?? resolvedType,
240
+ autoFocus: true
241
+ }
242
+ ),
243
+ error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }) : null,
244
+ renderBelowInput ? renderBelowInput({ draft, resolvedType, error, saving }) : null,
245
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
246
+ /* @__PURE__ */ jsxs(Button, { type: "submit", size: "sm", disabled: saving, children: [
247
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-3.5 w-3.5 animate-spin" }) : null,
248
+ computedSaveLabel
249
+ ] }),
250
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setEditingSafe(false), disabled: saving, children: t("ui.detail.inline.cancel", "Cancel") })
251
+ ] })
252
+ ]
253
+ }
254
+ ) : /* @__PURE__ */ jsx("div", { className: variant === "plain" ? "" : "mt-1", children: displayContent })
255
+ ] }),
256
+ renderActions ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: renderActions }) : null,
257
+ /* @__PURE__ */ jsx(
258
+ Button,
259
+ {
260
+ type: "button",
261
+ variant: "ghost",
262
+ size: triggerSize,
263
+ className: triggerClasses,
264
+ onClick: (event) => {
265
+ event.stopPropagation();
266
+ const next = !editing;
267
+ setEditingSafe(next);
268
+ },
269
+ children: editing ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
270
+ }
271
+ )
272
+ ] }) });
273
+ }
274
+ const isTestEnv = typeof process !== "undefined" && process.env.NODE_ENV === "test";
275
+ function MarkdownEditorFallback() {
276
+ const t = useT();
277
+ return /* @__PURE__ */ jsx(LoadingMessage, { label: t("ui.detail.inline.editorLoading", "Loading editor\u2026"), className: "min-h-[200px] justify-center" });
278
+ }
279
+ const MarkdownEditorTestStub = ({ value, onChange }) => /* @__PURE__ */ jsx(
280
+ Textarea,
281
+ {
282
+ "data-testid": "markdown-editor",
283
+ rows: 8,
284
+ value: value ?? "",
285
+ onChange: (event) => onChange?.(event.target.value)
286
+ }
287
+ );
288
+ const MarkdownEditorComponent = isTestEnv ? MarkdownEditorTestStub : dynamic(() => import("@uiw/react-md-editor"), {
289
+ ssr: false,
290
+ loading: () => /* @__PURE__ */ jsx(MarkdownEditorFallback, {})
291
+ });
292
+ const MarkdownPreviewComponent = isTestEnv ? ({ children, className }) => /* @__PURE__ */ jsx("div", { className, children }) : dynamic(() => import("react-markdown").then((mod) => mod.default), {
293
+ ssr: false,
294
+ loading: () => null
295
+ });
296
+ let markdownPluginsPromise = null;
297
+ async function loadMarkdownPlugins() {
298
+ if (isTestEnv) return [];
299
+ if (!markdownPluginsPromise) {
300
+ markdownPluginsPromise = import("remark-gfm").then((mod) => [mod.default ?? mod]).catch(() => []);
301
+ }
302
+ return markdownPluginsPromise;
303
+ }
304
+ function InlineMultilineEditor({
305
+ label,
306
+ value,
307
+ placeholder,
308
+ emptyLabel,
309
+ onSave,
310
+ validator,
311
+ variant = "default",
312
+ activateOnClick = true,
313
+ containerClassName,
314
+ triggerClassName,
315
+ renderDisplay
316
+ }) {
317
+ const t = useT();
318
+ const [editing, setEditing] = React.useState(false);
319
+ const [draft, setDraft] = React.useState(value ?? "");
320
+ const [error, setError] = React.useState(null);
321
+ const [saving, setSaving] = React.useState(false);
322
+ const [isMarkdownEnabled, setIsMarkdownEnabled] = React.useState(true);
323
+ const textareaRef = React.useRef(null);
324
+ const markdownEditorRef = React.useRef(null);
325
+ const [markdownPlugins, setMarkdownPlugins] = React.useState([]);
326
+ const fallbackError = React.useMemo(
327
+ () => t("ui.detail.inline.error", "Failed to save value."),
328
+ [t]
329
+ );
330
+ React.useEffect(() => {
331
+ if (isTestEnv) return;
332
+ let mounted = true;
333
+ void loadMarkdownPlugins().then((plugins) => {
334
+ if (!mounted) return;
335
+ setMarkdownPlugins(plugins);
336
+ });
337
+ return () => {
338
+ mounted = false;
339
+ };
340
+ }, []);
341
+ const adjustTextareaSize = React.useCallback((element) => {
342
+ if (!element) return;
343
+ element.style.height = "auto";
344
+ element.style.height = `${element.scrollHeight}px`;
345
+ }, []);
346
+ React.useEffect(() => {
347
+ adjustTextareaSize(textareaRef.current);
348
+ }, [adjustTextareaSize, draft, isMarkdownEnabled]);
349
+ React.useEffect(() => {
350
+ if (!editing) return;
351
+ if (isMarkdownEnabled) {
352
+ const element2 = markdownEditorRef.current?.querySelector("textarea");
353
+ if (!element2) return;
354
+ window.requestAnimationFrame(() => {
355
+ element2.focus();
356
+ });
357
+ return;
358
+ }
359
+ const element = textareaRef.current;
360
+ if (!element) return;
361
+ window.requestAnimationFrame(() => {
362
+ adjustTextareaSize(element);
363
+ element.focus();
364
+ });
365
+ }, [adjustTextareaSize, editing, isMarkdownEnabled]);
366
+ const handleMarkdownToggle = React.useCallback(() => {
367
+ setIsMarkdownEnabled((prev) => !prev);
368
+ }, []);
369
+ React.useEffect(() => {
370
+ if (!editing) {
371
+ setDraft(value ?? "");
372
+ setError(null);
373
+ }
374
+ }, [editing, value]);
375
+ const handleActivate = React.useCallback(() => {
376
+ if (!editing) setEditing(true);
377
+ }, [editing]);
378
+ const handleInteractiveClick = React.useCallback(
379
+ (event) => {
380
+ if (!activateOnClick || editing) return;
381
+ const target = event.target;
382
+ const interactiveElement = target.closest('button, input, select, textarea, a, [role="link"]');
383
+ if (interactiveElement) {
384
+ if (interactiveElement.tagName.toLowerCase() === "a") {
385
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
386
+ return;
387
+ }
388
+ event.preventDefault();
389
+ } else {
390
+ return;
391
+ }
392
+ }
393
+ handleActivate();
394
+ },
395
+ [activateOnClick, editing, handleActivate]
396
+ );
397
+ const handleContainerKeyDown = React.useCallback(
398
+ (event) => {
399
+ if (!activateOnClick || editing) return;
400
+ if (event.key === "Enter" || event.key === " ") {
401
+ event.preventDefault();
402
+ handleActivate();
403
+ }
404
+ },
405
+ [activateOnClick, editing, handleActivate]
406
+ );
407
+ const adjustError = React.useCallback(
408
+ (nextValue) => {
409
+ if (!validator) return null;
410
+ const trimmed = nextValue.trim();
411
+ return validator(trimmed);
412
+ },
413
+ [validator]
414
+ );
415
+ const containerClasses = cn(
416
+ "group rounded-lg border p-4",
417
+ variant === "muted" ? "bg-muted/20" : null,
418
+ activateOnClick && !editing ? "cursor-pointer" : null,
419
+ containerClassName ?? null
420
+ );
421
+ const triggerClasses = cn(
422
+ "transition-opacity duration-150",
423
+ editing ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100",
424
+ triggerClassName ?? null
425
+ );
426
+ const handleSave = React.useCallback(async () => {
427
+ const trimmed = draft.trim();
428
+ const validationError = adjustError(draft);
429
+ if (validationError) {
430
+ setError(validationError);
431
+ return;
432
+ }
433
+ setSaving(true);
434
+ try {
435
+ await onSave(trimmed.length ? trimmed : null);
436
+ setEditing(false);
437
+ } catch (err) {
438
+ setError(resolveInlineErrorMessage(err, fallbackError));
439
+ } finally {
440
+ setSaving(false);
441
+ }
442
+ }, [adjustError, draft, fallbackError, onSave]);
443
+ return /* @__PURE__ */ jsx("div", { className: containerClasses, onClick: handleInteractiveClick, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
444
+ /* @__PURE__ */ jsxs(
445
+ "div",
446
+ {
447
+ className: cn("flex-1 min-w-0", activateOnClick && !editing ? "cursor-pointer" : null),
448
+ ...activateOnClick && !editing ? { role: "button", tabIndex: 0, onKeyDown: handleContainerKeyDown } : {},
449
+ children: [
450
+ /* @__PURE__ */ jsx("p", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }),
451
+ editing ? /* @__PURE__ */ jsxs(
452
+ "form",
453
+ {
454
+ className: "mt-2 space-y-3",
455
+ onSubmit: (event) => {
456
+ event.preventDefault();
457
+ if (!saving) void handleSave();
458
+ },
459
+ onKeyDown: (event) => {
460
+ if (event.key === "Escape") {
461
+ event.preventDefault();
462
+ setEditing(false);
463
+ setError(null);
464
+ return;
465
+ }
466
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
467
+ event.preventDefault();
468
+ if (!saving) void handleSave();
469
+ }
470
+ },
471
+ children: [
472
+ isMarkdownEnabled ? /* @__PURE__ */ jsx(
473
+ "div",
474
+ {
475
+ ref: markdownEditorRef,
476
+ className: cn(
477
+ "w-full rounded-md border border-muted-foreground/30 bg-background p-2",
478
+ saving ? "pointer-events-none opacity-75" : null
479
+ ),
480
+ children: /* @__PURE__ */ jsx("div", { "data-color-mode": "light", className: "w-full", children: /* @__PURE__ */ jsx(
481
+ MarkdownEditorComponent,
482
+ {
483
+ value: draft,
484
+ height: 220,
485
+ onChange: (nextValue) => {
486
+ if (error) setError(null);
487
+ setDraft(typeof nextValue === "string" ? nextValue : "");
488
+ },
489
+ previewOptions: { remarkPlugins: markdownPlugins }
490
+ }
491
+ ) })
492
+ }
493
+ ) : /* @__PURE__ */ jsx(
494
+ Textarea,
495
+ {
496
+ ref: textareaRef,
497
+ rows: 3,
498
+ className: "w-full resize-none overflow-hidden rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
499
+ placeholder,
500
+ value: draft,
501
+ onChange: (event) => {
502
+ if (error) setError(null);
503
+ setDraft(event.target.value);
504
+ },
505
+ onInput: (event) => adjustTextareaSize(event.currentTarget),
506
+ autoFocus: true,
507
+ disabled: saving
508
+ }
509
+ ),
510
+ error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }) : null,
511
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
512
+ /* @__PURE__ */ jsxs(Button, { type: "submit", size: "sm", disabled: saving, children: [
513
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-3.5 w-3.5 animate-spin" }) : null,
514
+ t("ui.detail.inline.saveShortcut", "Save \u2318\u23CE / Ctrl+Enter")
515
+ ] }),
516
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setEditing(false), disabled: saving, children: t("ui.detail.inline.cancel", "Cancel") }),
517
+ /* @__PURE__ */ jsxs(
518
+ Button,
519
+ {
520
+ type: "button",
521
+ variant: "ghost",
522
+ size: "icon",
523
+ onClick: handleMarkdownToggle,
524
+ "aria-pressed": isMarkdownEnabled,
525
+ title: isMarkdownEnabled ? t("ui.detail.inline.markdownDisable", "Disable Markdown") : t("ui.detail.inline.markdownEnable", "Enable Markdown"),
526
+ "aria-label": isMarkdownEnabled ? t("ui.detail.inline.markdownDisable", "Disable Markdown") : t("ui.detail.inline.markdownEnable", "Enable Markdown"),
527
+ className: cn("h-8 w-8", isMarkdownEnabled ? "text-primary" : void 0),
528
+ disabled: saving,
529
+ children: [
530
+ /* @__PURE__ */ jsx(FileCode, { className: "h-4 w-4", "aria-hidden": true }),
531
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: isMarkdownEnabled ? t("ui.detail.inline.markdownDisable", "Disable Markdown") : t("ui.detail.inline.markdownEnable", "Enable Markdown") })
532
+ ]
533
+ }
534
+ )
535
+ ] })
536
+ ]
537
+ }
538
+ ) : /* @__PURE__ */ jsx(
539
+ "div",
540
+ {
541
+ className: cn(
542
+ "mt-1 text-sm break-words",
543
+ renderDisplay ? null : "whitespace-pre-wrap",
544
+ activateOnClick && !editing ? "cursor-pointer" : null
545
+ ),
546
+ children: renderDisplay ? renderDisplay({ value, emptyLabel }) : value && value.length ? /* @__PURE__ */ jsx(
547
+ MarkdownPreviewComponent,
548
+ {
549
+ remarkPlugins: markdownPlugins,
550
+ className: "prose prose-sm max-w-none text-foreground [&>*]:my-2 [&>*:last-child]:mb-0 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5",
551
+ children: value
552
+ }
553
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel })
554
+ }
555
+ )
556
+ ]
557
+ }
558
+ ),
559
+ /* @__PURE__ */ jsx(
560
+ Button,
561
+ {
562
+ type: "button",
563
+ variant: "ghost",
564
+ size: "sm",
565
+ className: triggerClasses,
566
+ onClick: (event) => {
567
+ event.stopPropagation();
568
+ setEditing((state) => !state);
569
+ },
570
+ children: editing ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
571
+ }
572
+ )
573
+ ] }) });
574
+ }
575
+ function InlineSelectEditor({
576
+ label,
577
+ value,
578
+ emptyLabel,
579
+ options,
580
+ onSave,
581
+ variant = "default",
582
+ activateOnClick = false,
583
+ containerClassName,
584
+ triggerClassName,
585
+ hideLabel = false,
586
+ renderEditor,
587
+ renderDisplay
588
+ }) {
589
+ const t = useT();
590
+ const [editing, setEditing] = React.useState(false);
591
+ const [draft, setDraft] = React.useState(value ?? "");
592
+ const [saving, setSaving] = React.useState(false);
593
+ React.useEffect(() => {
594
+ if (!editing) setDraft(value ?? "");
595
+ }, [editing, value]);
596
+ const containerClasses = cn(
597
+ "group",
598
+ variant === "muted" ? "relative rounded border bg-muted/30 p-3" : variant === "plain" ? "relative flex flex-col gap-1 rounded-none border-0 p-0" : "rounded-lg border bg-card p-4",
599
+ activateOnClick && !editing ? "cursor-pointer" : null,
600
+ containerClassName ?? null
601
+ );
602
+ const triggerClasses = cn(
603
+ "shrink-0 transition-opacity duration-150",
604
+ editing ? "opacity-100" : "opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100",
605
+ variant === "muted" ? "h-8 w-8" : null,
606
+ triggerClassName ?? null
607
+ );
608
+ const handleSave = React.useCallback(async () => {
609
+ setSaving(true);
610
+ try {
611
+ await onSave(draft.length ? draft : null);
612
+ setEditing(false);
613
+ } catch (err) {
614
+ const message = err instanceof Error ? err.message : t("ui.detail.inline.error", "Failed to save value.");
615
+ console.error(message, err);
616
+ } finally {
617
+ setSaving(false);
618
+ }
619
+ }, [draft, onSave, t]);
620
+ const selected = options.find((option) => option.value === value);
621
+ const interactiveProps = activateOnClick && !editing ? {
622
+ role: "button",
623
+ tabIndex: 0,
624
+ onClick: () => setEditing(true),
625
+ onKeyDown: (event) => {
626
+ if (event.key === "Enter" || event.key === " ") {
627
+ event.preventDefault();
628
+ setEditing(true);
629
+ }
630
+ }
631
+ } : {};
632
+ return /* @__PURE__ */ jsx("div", { className: containerClasses, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
633
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", ...interactiveProps, children: [
634
+ hideLabel ? null : /* @__PURE__ */ jsx("p", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }),
635
+ editing ? /* @__PURE__ */ jsxs("div", { className: variant === "plain" ? "space-y-2 pt-1" : "mt-2 space-y-2", children: [
636
+ renderEditor ? renderEditor({ value: draft, onChange: setDraft }) : /* @__PURE__ */ jsxs(
637
+ "select",
638
+ {
639
+ className: "w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
640
+ value: draft,
641
+ onChange: (event) => setDraft(event.target.value),
642
+ children: [
643
+ /* @__PURE__ */ jsx("option", { value: "", children: t("ui.detail.inline.select.placeholder", "Not set") }),
644
+ options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
645
+ ]
646
+ }
647
+ ),
648
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
649
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: () => void handleSave(), disabled: saving, children: [
650
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-3.5 w-3.5 animate-spin" }) : null,
651
+ t("ui.detail.inline.saveShortcut", "Save \u2318\u23CE / Ctrl+Enter")
652
+ ] }),
653
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setEditing(false), disabled: saving, children: t("ui.detail.inline.cancel", "Cancel") })
654
+ ] })
655
+ ] }) : /* @__PURE__ */ jsx("div", { className: variant === "plain" ? "flex items-center gap-2" : "mt-1 text-sm", children: renderDisplay ? renderDisplay({ value, emptyLabel }) : selected ? /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
656
+ /* @__PURE__ */ jsx("p", { className: "font-medium leading-tight", children: selected.label }),
657
+ selected.description ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: selected.description }) : null
658
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: emptyLabel }) })
659
+ ] }),
660
+ /* @__PURE__ */ jsx(
661
+ Button,
662
+ {
663
+ type: "button",
664
+ variant: "ghost",
665
+ size: variant === "plain" ? "icon" : "sm",
666
+ className: triggerClasses,
667
+ onClick: (event) => {
668
+ event.stopPropagation();
669
+ setEditing((state) => !state);
670
+ },
671
+ children: editing ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
672
+ }
673
+ )
674
+ ] }) });
675
+ }
676
+ export {
677
+ InlineMultilineEditor,
678
+ InlineSelectEditor,
679
+ InlineTextEditor
680
+ };
681
+ //# sourceMappingURL=InlineEditors.js.map