@almadar/ui 5.162.0 → 5.164.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.
@@ -24519,6 +24519,7 @@ var init_DashboardLayout = __esm({
24519
24519
  onNotificationClick,
24520
24520
  showThemeToggle = true,
24521
24521
  sidebarFooter,
24522
+ sidebarContent,
24522
24523
  onSignOut: onSignOutProp,
24523
24524
  currentPath,
24524
24525
  layoutMode = "sidebar",
@@ -24644,7 +24645,11 @@ var init_DashboardLayout = __esm({
24644
24645
  {
24645
24646
  as: "nav",
24646
24647
  gap: "none",
24647
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
24648
+ className: cn(
24649
+ "py-4 space-y-1 overflow-y-auto",
24650
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
24651
+ isRail ? "px-2" : "px-3"
24652
+ ),
24648
24653
  children: navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
24649
24654
  NavLink,
24650
24655
  {
@@ -24656,6 +24661,7 @@ var init_DashboardLayout = __esm({
24656
24661
  ))
24657
24662
  }
24658
24663
  ),
24664
+ sidebarContent && !isRail && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
24659
24665
  sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
24660
24666
  ]
24661
24667
  }
@@ -27065,7 +27071,17 @@ function fileIcon(name) {
27065
27071
  return "file";
27066
27072
  }
27067
27073
  }
27068
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
27074
+ function ancestorsOf(id, byId) {
27075
+ const out = /* @__PURE__ */ new Set();
27076
+ if (!id) return out;
27077
+ let cur = byId.get(id)?.parentId;
27078
+ while (cur && byId.has(cur) && !out.has(cur)) {
27079
+ out.add(cur);
27080
+ cur = byId.get(cur)?.parentId;
27081
+ }
27082
+ return out;
27083
+ }
27084
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
27069
27085
  var init_FileTree = __esm({
27070
27086
  "components/core/molecules/FileTree.tsx"() {
27071
27087
  "use client";
@@ -27156,42 +27172,77 @@ var init_FileTree = __esm({
27156
27172
  indent,
27157
27173
  childrenByParent,
27158
27174
  onNodeSelect,
27159
- defaultExpanded = false
27175
+ onNodeAction,
27176
+ nodeActionIcon,
27177
+ nodeActionLabel,
27178
+ selectedId,
27179
+ look,
27180
+ isExpanded,
27181
+ onToggle
27160
27182
  }) => {
27161
- const [expanded, setExpanded] = React85.useState(defaultExpanded || depth < 1);
27162
27183
  const children = childrenByParent.get(item.id);
27163
27184
  const hasChildren = !!children && children.length > 0;
27185
+ const expanded = hasChildren && isExpanded(item.id, depth);
27186
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
27187
+ const nav = look === "nav";
27164
27188
  const handleClick = React85.useCallback(() => {
27165
- if (hasChildren) setExpanded((prev) => !prev);
27189
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
27166
27190
  onNodeSelect?.(item.id);
27167
- }, [hasChildren, item.id, onNodeSelect]);
27191
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
27192
+ const handleChevron = React85.useCallback((e) => {
27193
+ e.stopPropagation();
27194
+ onToggle(item.id, expanded);
27195
+ }, [item.id, expanded, onToggle]);
27196
+ const handleAction = React85.useCallback((e) => {
27197
+ e.stopPropagation();
27198
+ onNodeAction?.(item.id);
27199
+ }, [item.id, onNodeAction]);
27168
27200
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
27169
27201
  /* @__PURE__ */ jsxRuntime.jsxs(
27170
27202
  Box,
27171
27203
  {
27172
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27204
+ className: `group/treerow flex items-center gap-1.5 px-2 cursor-pointer rounded-sm transition-colors ${nav ? "py-1" : "py-0.5"} ${isSelected ? "bg-primary text-primary-foreground" : nav ? "text-foreground hover:bg-muted" : "hover:bg-muted"}`,
27173
27205
  style: { paddingLeft: depth * indent + 8 },
27174
27206
  onClick: handleClick,
27175
27207
  role: "treeitem",
27208
+ "aria-selected": isSelected,
27176
27209
  "aria-expanded": hasChildren ? expanded : void 0,
27177
27210
  children: [
27178
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
27211
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
27179
27212
  Icon,
27180
27213
  {
27181
27214
  name: expanded ? "chevron-down" : "chevron-right",
27182
27215
  size: "xs",
27183
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27216
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
27184
27217
  }
27185
- ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27218
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27186
27219
  /* @__PURE__ */ jsxRuntime.jsx(
27187
27220
  Icon,
27188
27221
  {
27189
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27222
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27190
27223
  size: "xs",
27191
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27224
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
27192
27225
  }
27193
27226
  ),
27194
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27227
+ /* @__PURE__ */ jsxRuntime.jsx(
27228
+ Typography,
27229
+ {
27230
+ variant: "caption",
27231
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
27232
+ children: item.label
27233
+ }
27234
+ ),
27235
+ onNodeAction && /* @__PURE__ */ jsxRuntime.jsx(
27236
+ Box,
27237
+ {
27238
+ onClick: handleAction,
27239
+ role: "button",
27240
+ "aria-label": nodeActionLabel ?? "Node action",
27241
+ title: nodeActionLabel,
27242
+ className: `ml-auto flex-shrink-0 rounded-sm p-0.5 opacity-0 group-hover/treerow:opacity-100 transition-opacity ${isSelected ? "hover:bg-primary-foreground/20" : "hover:bg-border"}`,
27243
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
27244
+ }
27245
+ )
27195
27246
  ]
27196
27247
  }
27197
27248
  ),
@@ -27202,47 +27253,116 @@ var init_FileTree = __esm({
27202
27253
  depth: depth + 1,
27203
27254
  indent,
27204
27255
  childrenByParent,
27205
- onNodeSelect
27256
+ onNodeSelect,
27257
+ onNodeAction,
27258
+ nodeActionIcon,
27259
+ nodeActionLabel,
27260
+ selectedId,
27261
+ look,
27262
+ isExpanded,
27263
+ onToggle
27206
27264
  },
27207
27265
  child.id
27208
27266
  )) })
27209
27267
  ] });
27210
27268
  };
27269
+ FlatFileTree = ({
27270
+ items,
27271
+ selectedId,
27272
+ look = "files",
27273
+ onNodeSelect,
27274
+ onNodeAction,
27275
+ nodeActionIcon,
27276
+ nodeActionLabel,
27277
+ className,
27278
+ indent = 16
27279
+ }) => {
27280
+ const [overrides, setOverrides] = React85.useState(() => /* @__PURE__ */ new Map());
27281
+ const byId = React85__namespace.default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
27282
+ const selectedAncestors = React85__namespace.default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
27283
+ const lastSelectedRef = React85__namespace.default.useRef(selectedId);
27284
+ React85__namespace.default.useEffect(() => {
27285
+ if (selectedId === lastSelectedRef.current) return;
27286
+ lastSelectedRef.current = selectedId;
27287
+ setOverrides((prev) => {
27288
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
27289
+ const next = new Map(prev);
27290
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
27291
+ return next;
27292
+ });
27293
+ }, [selectedId, selectedAncestors]);
27294
+ const isExpanded = React85.useCallback(
27295
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
27296
+ [overrides, selectedAncestors]
27297
+ );
27298
+ const onToggle = React85.useCallback((id, effective) => {
27299
+ setOverrides((prev) => {
27300
+ const next = new Map(prev);
27301
+ next.set(id, !effective);
27302
+ return next;
27303
+ });
27304
+ }, []);
27305
+ if (items.length === 0) return null;
27306
+ const ids = new Set(items.map((node) => node.id));
27307
+ const childrenByParent = /* @__PURE__ */ new Map();
27308
+ const roots = [];
27309
+ for (const item of items) {
27310
+ if (item.parentId && ids.has(item.parentId)) {
27311
+ const siblings = childrenByParent.get(item.parentId);
27312
+ if (siblings) siblings.push(item);
27313
+ else childrenByParent.set(item.parentId, [item]);
27314
+ } else {
27315
+ roots.push(item);
27316
+ }
27317
+ }
27318
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27319
+ FlatTreeNodeItem,
27320
+ {
27321
+ item,
27322
+ depth: 0,
27323
+ indent,
27324
+ childrenByParent,
27325
+ onNodeSelect,
27326
+ onNodeAction,
27327
+ nodeActionIcon,
27328
+ nodeActionLabel,
27329
+ selectedId,
27330
+ look,
27331
+ isExpanded,
27332
+ onToggle
27333
+ },
27334
+ item.id
27335
+ )) });
27336
+ };
27211
27337
  FileTree = ({
27212
27338
  tree,
27213
27339
  items,
27214
27340
  selectedPath,
27341
+ selectedId,
27342
+ look = "files",
27215
27343
  onFileSelect,
27216
27344
  onNodeSelect,
27345
+ onNodeAction,
27346
+ nodeActionIcon,
27347
+ nodeActionLabel,
27217
27348
  className,
27218
27349
  indent = 16
27219
27350
  }) => {
27220
27351
  if (items) {
27221
- if (items.length === 0) return null;
27222
- const ids = new Set(items.map((node) => node.id));
27223
- const childrenByParent = /* @__PURE__ */ new Map();
27224
- const roots = [];
27225
- for (const item of items) {
27226
- if (item.parentId && ids.has(item.parentId)) {
27227
- const siblings = childrenByParent.get(item.parentId);
27228
- if (siblings) siblings.push(item);
27229
- else childrenByParent.set(item.parentId, [item]);
27230
- } else {
27231
- roots.push(item);
27232
- }
27233
- }
27234
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27235
- FlatTreeNodeItem,
27352
+ return /* @__PURE__ */ jsxRuntime.jsx(
27353
+ FlatFileTree,
27236
27354
  {
27237
- item,
27238
- depth: 0,
27239
- indent,
27240
- childrenByParent,
27355
+ items,
27356
+ selectedId,
27357
+ look,
27241
27358
  onNodeSelect,
27242
- defaultExpanded: true
27243
- },
27244
- item.id
27245
- )) });
27359
+ onNodeAction,
27360
+ nodeActionIcon,
27361
+ nodeActionLabel,
27362
+ className,
27363
+ indent
27364
+ }
27365
+ );
27246
27366
  }
27247
27367
  if (!tree || tree.length === 0) return null;
27248
27368
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -32643,6 +32763,33 @@ var init_Lightbox = __esm({
32643
32763
  Lightbox.displayName = "Lightbox";
32644
32764
  }
32645
32765
  });
32766
+
32767
+ // lib/relationLabel.ts
32768
+ function relationLabel(value) {
32769
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
32770
+ return null;
32771
+ for (const key of ["name", "title", "label"]) {
32772
+ const candidate = value[key];
32773
+ if (typeof candidate === "string" && candidate !== "") return candidate;
32774
+ }
32775
+ const id = value.id;
32776
+ return id !== void 0 && id !== null ? String(id) : null;
32777
+ }
32778
+ function relationDisplayLabels(value, options) {
32779
+ if (value === void 0 || value === null || value === "") return [];
32780
+ if (Array.isArray(value)) {
32781
+ return value.flatMap((item) => relationDisplayLabels(item, options));
32782
+ }
32783
+ const hydrated = relationLabel(value);
32784
+ if (hydrated !== null) return [hydrated];
32785
+ const raw = String(value);
32786
+ const match = options?.find((opt) => opt.value === raw);
32787
+ return [match ? match.label : raw];
32788
+ }
32789
+ var init_relationLabel = __esm({
32790
+ "lib/relationLabel.ts"() {
32791
+ }
32792
+ });
32646
32793
  function renderIconInput3(icon, props) {
32647
32794
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
32648
32795
  }
@@ -32960,6 +33107,7 @@ var init_TableView = __esm({
32960
33107
  "use client";
32961
33108
  init_cn();
32962
33109
  init_format();
33110
+ init_relationLabel();
32963
33111
  init_getNestedValue();
32964
33112
  init_useEventBus();
32965
33113
  init_Box();
@@ -32973,7 +33121,13 @@ var init_TableView = __esm({
32973
33121
  init_Menu();
32974
33122
  init_useDataDnd();
32975
33123
  tableViewLog = logger.createLogger("almadar:ui:table-view");
32976
- formatCell = (value, format) => formatValue(value, format);
33124
+ formatCell = (value, format) => {
33125
+ if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
33126
+ const labels = relationDisplayLabels(value);
33127
+ if (labels.length > 0) return labels.join(", ");
33128
+ }
33129
+ return formatValue(value, format);
33130
+ };
32977
33131
  MAX_MEASURED_COL_CH = 32;
32978
33132
  BADGE_CHROME_CH = 4;
32979
33133
  alignClass = {
@@ -36450,6 +36604,35 @@ var init_RichTextEditor = __esm({
36450
36604
  document.execCommand("createLink", false, safe);
36451
36605
  afterEdit();
36452
36606
  }, [afterEdit, t, toolbar.link]);
36607
+ const imageInputRef = React85.useRef(null);
36608
+ const savedRangeRef = React85.useRef(null);
36609
+ const execImage = React85.useCallback(() => {
36610
+ const root = ref.current;
36611
+ const sel = window.getSelection();
36612
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
36613
+ imageInputRef.current?.click();
36614
+ }, []);
36615
+ const handleImageFile = React85.useCallback((e) => {
36616
+ const file = e.target.files?.[0];
36617
+ e.target.value = "";
36618
+ if (!file) return;
36619
+ const reader = new FileReader();
36620
+ reader.onload = () => {
36621
+ const url = typeof reader.result === "string" ? reader.result : "";
36622
+ if (!url.startsWith("data:image/")) return;
36623
+ const root = ref.current;
36624
+ if (!root) return;
36625
+ root.focus();
36626
+ const sel = window.getSelection();
36627
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
36628
+ sel.removeAllRanges();
36629
+ sel.addRange(savedRangeRef.current);
36630
+ }
36631
+ document.execCommand("insertImage", false, url);
36632
+ afterEdit();
36633
+ };
36634
+ reader.readAsDataURL(file);
36635
+ }, [afterEdit]);
36453
36636
  const execRule = React85.useCallback(() => {
36454
36637
  document.execCommand("insertHorizontalRule", false);
36455
36638
  afterEdit();
@@ -36462,6 +36645,18 @@ var init_RichTextEditor = __esm({
36462
36645
  }
36463
36646
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
36464
36647
  /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
36648
+ /* @__PURE__ */ jsxRuntime.jsx(
36649
+ "input",
36650
+ {
36651
+ ref: imageInputRef,
36652
+ type: "file",
36653
+ accept: "image/*",
36654
+ className: "hidden",
36655
+ "aria-hidden": "true",
36656
+ tabIndex: -1,
36657
+ onChange: handleImageFile
36658
+ }
36659
+ ),
36465
36660
  showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
36466
36661
  Box,
36467
36662
  {
@@ -36487,6 +36682,7 @@ var init_RichTextEditor = __esm({
36487
36682
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
36488
36683
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
36489
36684
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Link, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
36685
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Image, label: t("richTextEditor.image"), onExec: execImage }),
36490
36686
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Minus, label: t("richTextEditor.divider"), onExec: execRule })
36491
36687
  ]
36492
36688
  }
@@ -36527,6 +36723,8 @@ function DocumentPanel({
36527
36723
  id,
36528
36724
  title,
36529
36725
  subtitle,
36726
+ coverImage,
36727
+ icon,
36530
36728
  value,
36531
36729
  actions,
36532
36730
  maxInlineActions,
@@ -36602,62 +36800,74 @@ function DocumentPanel({
36602
36800
  const emitWithId = (event) => () => {
36603
36801
  if (event) eventBus.emit(`UI:${event}`, { id: recordId });
36604
36802
  };
36605
- return /* @__PURE__ */ jsxRuntime.jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
36606
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
36607
- /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
36608
- titleNode,
36609
- subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
36610
- ] }),
36611
- /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36612
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
36613
- /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
36614
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36615
- editEvent && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
36616
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
36617
- editLabel ?? (t("documentPanel.edit") || "Edit")
36618
- ] }),
36619
- inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
36620
- Button,
36621
- {
36622
- variant: "primary",
36623
- size: "sm",
36624
- onClick: () => fireAction(action),
36625
- "data-testid": `action-${action.event}`,
36626
- children: [
36627
- action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
36628
- action.label
36629
- ]
36630
- },
36631
- idx
36632
- )),
36633
- menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
36634
- Menu,
36635
- {
36636
- position: "bottom-end",
36637
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
36638
- items: menuActions.map((action) => ({
36639
- label: action.label,
36640
- icon: action.icon,
36641
- variant: action.variant === "danger" ? "danger" : "default",
36642
- onClick: () => fireAction(action)
36643
- }))
36644
- }
36645
- )
36646
- ] }) })
36647
- ] }),
36648
- editing ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
36649
- Box,
36803
+ return /* @__PURE__ */ jsxRuntime.jsxs(Card, { variant: "elevated", className: cn("w-full overflow-hidden", className), children: [
36804
+ coverImage ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "h-44 w-full sm:h-52", "data-testid": "document-cover", children: /* @__PURE__ */ jsxRuntime.jsx(
36805
+ "img",
36650
36806
  {
36651
- onClick: emitWithId(editEvent),
36652
- className: cn(
36653
- "rounded-md px-1 -mx-1 min-h-[16rem]",
36654
- editEvent && "cursor-text transition-colors hover:bg-muted/30"
36655
- ),
36656
- "data-testid": "document-body",
36657
- children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
36658
- }
36659
- )
36660
- ] }) });
36807
+ src: coverImage,
36808
+ alt: "",
36809
+ "aria-hidden": "true",
36810
+ className: "h-full w-full object-cover"
36811
+ }
36812
+ ) }) : null,
36813
+ /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
36814
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
36815
+ /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
36816
+ icon ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-muted-foreground", "data-testid": "document-icon", children: renderIconInput4(icon, { size: "lg" }) }) : null,
36817
+ titleNode,
36818
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
36819
+ ] }),
36820
+ /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36821
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
36822
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
36823
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36824
+ editEvent && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
36825
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
36826
+ editLabel ?? (t("documentPanel.edit") || "Edit")
36827
+ ] }),
36828
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
36829
+ Button,
36830
+ {
36831
+ variant: "primary",
36832
+ size: "sm",
36833
+ onClick: () => fireAction(action),
36834
+ "data-testid": `action-${action.event}`,
36835
+ children: [
36836
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
36837
+ action.label
36838
+ ]
36839
+ },
36840
+ idx
36841
+ )),
36842
+ menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
36843
+ Menu,
36844
+ {
36845
+ position: "bottom-end",
36846
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
36847
+ items: menuActions.map((action) => ({
36848
+ label: action.label,
36849
+ icon: action.icon,
36850
+ variant: action.variant === "danger" ? "danger" : "default",
36851
+ onClick: () => fireAction(action)
36852
+ }))
36853
+ }
36854
+ )
36855
+ ] }) })
36856
+ ] }),
36857
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
36858
+ Box,
36859
+ {
36860
+ onClick: emitWithId(editEvent),
36861
+ className: cn(
36862
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
36863
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
36864
+ ),
36865
+ "data-testid": "document-body",
36866
+ children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
36867
+ }
36868
+ )
36869
+ ] })
36870
+ ] });
36661
36871
  }
36662
36872
  var init_DocumentPanel = __esm({
36663
36873
  "components/core/molecules/DocumentPanel.tsx"() {
@@ -36681,17 +36891,16 @@ function renderIconInput5(icon, props) {
36681
36891
  function fieldName(field) {
36682
36892
  return field.name ?? field.key ?? "";
36683
36893
  }
36684
- function relationLabel(value) {
36685
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
36686
- for (const key of ["name", "title", "label"]) {
36687
- const candidate = value[key];
36688
- if (typeof candidate === "string" && candidate !== "") return candidate;
36894
+ function relationId(value) {
36895
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
36896
+ const id = value.id;
36897
+ if (id !== void 0 && id !== null) return String(id);
36689
36898
  }
36690
- const id = value.id;
36691
- return id !== void 0 && id !== null ? String(id) : null;
36899
+ return String(value);
36692
36900
  }
36693
36901
  function fieldKind(field, value) {
36694
36902
  if (field.kind) return field.kind;
36903
+ if (field.type === "image") return "image";
36695
36904
  if (field.options && field.options.length > 0) return "select";
36696
36905
  if (typeof value === "boolean" || field.format === "boolean") return "boolean";
36697
36906
  if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
@@ -36701,6 +36910,7 @@ function DocumentDetails({
36701
36910
  entity,
36702
36911
  fields,
36703
36912
  metaCommitEvent,
36913
+ relationEvent,
36704
36914
  title,
36705
36915
  className
36706
36916
  }) {
@@ -36715,11 +36925,51 @@ function DocumentDetails({
36715
36925
  if (!metaCommitEvent || next === current) return;
36716
36926
  eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
36717
36927
  };
36928
+ const relationChip = (item, name, key) => {
36929
+ const label = relationLabel(item) ?? humanizeEnumValue(String(item));
36930
+ if (!relationEvent) {
36931
+ return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: label }, key);
36932
+ }
36933
+ const emitClick = () => eventBus.emit(`UI:${relationEvent}`, { field: name, id: relationId(item), name: label });
36934
+ return /* @__PURE__ */ jsxRuntime.jsx(
36935
+ Badge,
36936
+ {
36937
+ variant: "default",
36938
+ role: "button",
36939
+ tabIndex: 0,
36940
+ className: "cursor-pointer transition-colors hover:bg-accent",
36941
+ onClick: emitClick,
36942
+ onKeyDown: (e) => {
36943
+ if (e.key === "Enter" || e.key === " ") {
36944
+ e.preventDefault();
36945
+ emitClick();
36946
+ }
36947
+ },
36948
+ "data-testid": `relation-chip-${name}`,
36949
+ children: label
36950
+ },
36951
+ key
36952
+ );
36953
+ };
36718
36954
  const renderValue = (field) => {
36719
36955
  const name = fieldName(field);
36720
36956
  const raw = getNestedValue(entity ?? {}, name);
36721
36957
  const kind = fieldKind(field, raw);
36722
36958
  const label = field.label ?? field.header ?? humanizeFieldName(name);
36959
+ if (kind === "image") {
36960
+ const url = typeof raw === "string" ? raw : "";
36961
+ if (!url) return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
36962
+ return /* @__PURE__ */ jsxRuntime.jsx(
36963
+ "img",
36964
+ {
36965
+ src: url,
36966
+ alt: label,
36967
+ loading: "lazy",
36968
+ className: "max-h-32 w-full rounded-md border border-border object-cover",
36969
+ "data-testid": `document-property-image-${name}`
36970
+ }
36971
+ );
36972
+ }
36723
36973
  if (kind === "boolean") {
36724
36974
  return /* @__PURE__ */ jsxRuntime.jsx(
36725
36975
  Switch,
@@ -36749,9 +36999,12 @@ function DocumentDetails({
36749
36999
  if (raw.length === 0) {
36750
37000
  return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
36751
37001
  }
36752
- return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
37002
+ return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => relationChip(item, name, i)) });
36753
37003
  }
36754
- const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
37004
+ if (raw !== void 0 && raw !== null && raw !== "" && relationLabel(raw) !== null) {
37005
+ return relationChip(raw, name);
37006
+ }
37007
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : formatValue(raw, field.format);
36755
37008
  return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
36756
37009
  }
36757
37010
  if (fieldDraft?.name === name) {
@@ -36820,6 +37073,7 @@ var init_DocumentDetails = __esm({
36820
37073
  init_cn();
36821
37074
  init_format();
36822
37075
  init_getNestedValue();
37076
+ init_relationLabel();
36823
37077
  init_useEventBus();
36824
37078
  init_Box();
36825
37079
  init_Stack();
@@ -41814,7 +42068,18 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41814
42068
  if (value === void 0 || value === null) return "\u2014";
41815
42069
  const str = String(value);
41816
42070
  switch (fieldType) {
41817
- case "image":
42071
+ case "image": {
42072
+ if (!str) return "\u2014";
42073
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
42074
+ "img",
42075
+ {
42076
+ src: str,
42077
+ alt: formatFieldLabel(fieldName2),
42078
+ className: "max-w-full max-h-64 rounded-md object-contain",
42079
+ loading: "lazy"
42080
+ }
42081
+ ) });
42082
+ }
41818
42083
  case "url": {
41819
42084
  if (str.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str.startsWith("data:image/")) {
41820
42085
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -41827,7 +42092,7 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41827
42092
  }
41828
42093
  ) });
41829
42094
  }
41830
- if (fieldType === "url" && /^https?:\/\//i.test(str)) {
42095
+ if (/^https?:\/\//i.test(str)) {
41831
42096
  return /* @__PURE__ */ jsxRuntime.jsx(
41832
42097
  "a",
41833
42098
  {
@@ -41913,8 +42178,10 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41913
42178
  return str;
41914
42179
  }
41915
42180
  case "relation": {
41916
- const match = meta?.options?.find((opt) => opt.value === str);
41917
- return match ? match.label : str;
42181
+ const labels = relationDisplayLabels(value, meta?.options);
42182
+ if (labels.length === 0) return "\u2014";
42183
+ if (labels.length === 1) return labels[0];
42184
+ return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", wrap: true, children: labels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: label }, i)) });
41918
42185
  }
41919
42186
  case "file": {
41920
42187
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -42016,6 +42283,7 @@ var init_DetailPanel = __esm({
42016
42283
  init_cn();
42017
42284
  init_format();
42018
42285
  init_getNestedValue();
42286
+ init_relationLabel();
42019
42287
  init_useEventBus();
42020
42288
  init_UploadDropZone();
42021
42289
  formatFieldLabel = humanizeFieldName;
@@ -42674,6 +42942,8 @@ function determineInputType(field) {
42674
42942
  return "url";
42675
42943
  case "file":
42676
42944
  return "file";
42945
+ case "image":
42946
+ return "image";
42677
42947
  case "money":
42678
42948
  return "currency";
42679
42949
  case "number":
@@ -43273,6 +43543,50 @@ var init_Form = __esm({
43273
43543
  }
43274
43544
  }
43275
43545
  );
43546
+ case "image": {
43547
+ const imageUrl = typeof currentValue === "string" ? currentValue : "";
43548
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
43549
+ imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
43550
+ "img",
43551
+ {
43552
+ src: imageUrl,
43553
+ alt: "",
43554
+ "aria-hidden": "true",
43555
+ className: "h-24 w-full max-w-xs rounded-md border border-border object-cover",
43556
+ "data-testid": `image-preview-${fieldName2}`
43557
+ }
43558
+ ) : null,
43559
+ /* @__PURE__ */ jsxRuntime.jsx(
43560
+ UploadDropZone,
43561
+ {
43562
+ accept: "image/*",
43563
+ maxFiles: 1,
43564
+ maxSize: 2 * 1024 * 1024,
43565
+ disabled: isLoading,
43566
+ onFiles: (files) => {
43567
+ const f3 = files[0];
43568
+ if (!f3) return;
43569
+ const reader = new FileReader();
43570
+ reader.onload = () => handleChange(
43571
+ fieldName2,
43572
+ typeof reader.result === "string" ? reader.result : ""
43573
+ );
43574
+ reader.readAsDataURL(f3);
43575
+ }
43576
+ }
43577
+ ),
43578
+ /* @__PURE__ */ jsxRuntime.jsx(
43579
+ Input,
43580
+ {
43581
+ ...commonProps,
43582
+ type: "url",
43583
+ placeholder: t("form.imageUrlFallback"),
43584
+ value: imageUrl,
43585
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43586
+ }
43587
+ )
43588
+ ] });
43589
+ }
43276
43590
  case "date":
43277
43591
  return /* @__PURE__ */ jsxRuntime.jsx(
43278
43592
  Input,