@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.
@@ -24445,6 +24445,7 @@ var init_DashboardLayout = __esm({
24445
24445
  onNotificationClick,
24446
24446
  showThemeToggle = true,
24447
24447
  sidebarFooter,
24448
+ sidebarContent,
24448
24449
  onSignOut: onSignOutProp,
24449
24450
  currentPath,
24450
24451
  layoutMode = "sidebar",
@@ -24570,7 +24571,11 @@ var init_DashboardLayout = __esm({
24570
24571
  {
24571
24572
  as: "nav",
24572
24573
  gap: "none",
24573
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
24574
+ className: cn(
24575
+ "py-4 space-y-1 overflow-y-auto",
24576
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
24577
+ isRail ? "px-2" : "px-3"
24578
+ ),
24574
24579
  children: navItems.map((item) => /* @__PURE__ */ jsx(
24575
24580
  NavLink,
24576
24581
  {
@@ -24582,6 +24587,7 @@ var init_DashboardLayout = __esm({
24582
24587
  ))
24583
24588
  }
24584
24589
  ),
24590
+ sidebarContent && !isRail && /* @__PURE__ */ jsx(Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
24585
24591
  sidebarFooter && /* @__PURE__ */ jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
24586
24592
  ]
24587
24593
  }
@@ -26991,7 +26997,17 @@ function fileIcon(name) {
26991
26997
  return "file";
26992
26998
  }
26993
26999
  }
26994
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
27000
+ function ancestorsOf(id, byId) {
27001
+ const out = /* @__PURE__ */ new Set();
27002
+ if (!id) return out;
27003
+ let cur = byId.get(id)?.parentId;
27004
+ while (cur && byId.has(cur) && !out.has(cur)) {
27005
+ out.add(cur);
27006
+ cur = byId.get(cur)?.parentId;
27007
+ }
27008
+ return out;
27009
+ }
27010
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
26995
27011
  var init_FileTree = __esm({
26996
27012
  "components/core/molecules/FileTree.tsx"() {
26997
27013
  "use client";
@@ -27082,42 +27098,77 @@ var init_FileTree = __esm({
27082
27098
  indent,
27083
27099
  childrenByParent,
27084
27100
  onNodeSelect,
27085
- defaultExpanded = false
27101
+ onNodeAction,
27102
+ nodeActionIcon,
27103
+ nodeActionLabel,
27104
+ selectedId,
27105
+ look,
27106
+ isExpanded,
27107
+ onToggle
27086
27108
  }) => {
27087
- const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
27088
27109
  const children = childrenByParent.get(item.id);
27089
27110
  const hasChildren = !!children && children.length > 0;
27111
+ const expanded = hasChildren && isExpanded(item.id, depth);
27112
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
27113
+ const nav = look === "nav";
27090
27114
  const handleClick = useCallback(() => {
27091
- if (hasChildren) setExpanded((prev) => !prev);
27115
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
27092
27116
  onNodeSelect?.(item.id);
27093
- }, [hasChildren, item.id, onNodeSelect]);
27117
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
27118
+ const handleChevron = useCallback((e) => {
27119
+ e.stopPropagation();
27120
+ onToggle(item.id, expanded);
27121
+ }, [item.id, expanded, onToggle]);
27122
+ const handleAction = useCallback((e) => {
27123
+ e.stopPropagation();
27124
+ onNodeAction?.(item.id);
27125
+ }, [item.id, onNodeAction]);
27094
27126
  return /* @__PURE__ */ jsxs(Fragment, { children: [
27095
27127
  /* @__PURE__ */ jsxs(
27096
27128
  Box,
27097
27129
  {
27098
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27130
+ 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"}`,
27099
27131
  style: { paddingLeft: depth * indent + 8 },
27100
27132
  onClick: handleClick,
27101
27133
  role: "treeitem",
27134
+ "aria-selected": isSelected,
27102
27135
  "aria-expanded": hasChildren ? expanded : void 0,
27103
27136
  children: [
27104
- hasChildren ? /* @__PURE__ */ jsx(
27137
+ hasChildren ? /* @__PURE__ */ jsx(Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsx(
27105
27138
  Icon,
27106
27139
  {
27107
27140
  name: expanded ? "chevron-down" : "chevron-right",
27108
27141
  size: "xs",
27109
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27142
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
27110
27143
  }
27111
- ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27144
+ ) }) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27112
27145
  /* @__PURE__ */ jsx(
27113
27146
  Icon,
27114
27147
  {
27115
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27148
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27116
27149
  size: "xs",
27117
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27150
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
27118
27151
  }
27119
27152
  ),
27120
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27153
+ /* @__PURE__ */ jsx(
27154
+ Typography,
27155
+ {
27156
+ variant: "caption",
27157
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
27158
+ children: item.label
27159
+ }
27160
+ ),
27161
+ onNodeAction && /* @__PURE__ */ jsx(
27162
+ Box,
27163
+ {
27164
+ onClick: handleAction,
27165
+ role: "button",
27166
+ "aria-label": nodeActionLabel ?? "Node action",
27167
+ title: nodeActionLabel,
27168
+ 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"}`,
27169
+ children: /* @__PURE__ */ jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
27170
+ }
27171
+ )
27121
27172
  ]
27122
27173
  }
27123
27174
  ),
@@ -27128,47 +27179,116 @@ var init_FileTree = __esm({
27128
27179
  depth: depth + 1,
27129
27180
  indent,
27130
27181
  childrenByParent,
27131
- onNodeSelect
27182
+ onNodeSelect,
27183
+ onNodeAction,
27184
+ nodeActionIcon,
27185
+ nodeActionLabel,
27186
+ selectedId,
27187
+ look,
27188
+ isExpanded,
27189
+ onToggle
27132
27190
  },
27133
27191
  child.id
27134
27192
  )) })
27135
27193
  ] });
27136
27194
  };
27195
+ FlatFileTree = ({
27196
+ items,
27197
+ selectedId,
27198
+ look = "files",
27199
+ onNodeSelect,
27200
+ onNodeAction,
27201
+ nodeActionIcon,
27202
+ nodeActionLabel,
27203
+ className,
27204
+ indent = 16
27205
+ }) => {
27206
+ const [overrides, setOverrides] = useState(() => /* @__PURE__ */ new Map());
27207
+ const byId = React85__default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
27208
+ const selectedAncestors = React85__default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
27209
+ const lastSelectedRef = React85__default.useRef(selectedId);
27210
+ React85__default.useEffect(() => {
27211
+ if (selectedId === lastSelectedRef.current) return;
27212
+ lastSelectedRef.current = selectedId;
27213
+ setOverrides((prev) => {
27214
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
27215
+ const next = new Map(prev);
27216
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
27217
+ return next;
27218
+ });
27219
+ }, [selectedId, selectedAncestors]);
27220
+ const isExpanded = useCallback(
27221
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
27222
+ [overrides, selectedAncestors]
27223
+ );
27224
+ const onToggle = useCallback((id, effective) => {
27225
+ setOverrides((prev) => {
27226
+ const next = new Map(prev);
27227
+ next.set(id, !effective);
27228
+ return next;
27229
+ });
27230
+ }, []);
27231
+ if (items.length === 0) return null;
27232
+ const ids = new Set(items.map((node) => node.id));
27233
+ const childrenByParent = /* @__PURE__ */ new Map();
27234
+ const roots = [];
27235
+ for (const item of items) {
27236
+ if (item.parentId && ids.has(item.parentId)) {
27237
+ const siblings = childrenByParent.get(item.parentId);
27238
+ if (siblings) siblings.push(item);
27239
+ else childrenByParent.set(item.parentId, [item]);
27240
+ } else {
27241
+ roots.push(item);
27242
+ }
27243
+ }
27244
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
27245
+ FlatTreeNodeItem,
27246
+ {
27247
+ item,
27248
+ depth: 0,
27249
+ indent,
27250
+ childrenByParent,
27251
+ onNodeSelect,
27252
+ onNodeAction,
27253
+ nodeActionIcon,
27254
+ nodeActionLabel,
27255
+ selectedId,
27256
+ look,
27257
+ isExpanded,
27258
+ onToggle
27259
+ },
27260
+ item.id
27261
+ )) });
27262
+ };
27137
27263
  FileTree = ({
27138
27264
  tree,
27139
27265
  items,
27140
27266
  selectedPath,
27267
+ selectedId,
27268
+ look = "files",
27141
27269
  onFileSelect,
27142
27270
  onNodeSelect,
27271
+ onNodeAction,
27272
+ nodeActionIcon,
27273
+ nodeActionLabel,
27143
27274
  className,
27144
27275
  indent = 16
27145
27276
  }) => {
27146
27277
  if (items) {
27147
- if (items.length === 0) return null;
27148
- const ids = new Set(items.map((node) => node.id));
27149
- const childrenByParent = /* @__PURE__ */ new Map();
27150
- const roots = [];
27151
- for (const item of items) {
27152
- if (item.parentId && ids.has(item.parentId)) {
27153
- const siblings = childrenByParent.get(item.parentId);
27154
- if (siblings) siblings.push(item);
27155
- else childrenByParent.set(item.parentId, [item]);
27156
- } else {
27157
- roots.push(item);
27158
- }
27159
- }
27160
- return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
27161
- FlatTreeNodeItem,
27278
+ return /* @__PURE__ */ jsx(
27279
+ FlatFileTree,
27162
27280
  {
27163
- item,
27164
- depth: 0,
27165
- indent,
27166
- childrenByParent,
27281
+ items,
27282
+ selectedId,
27283
+ look,
27167
27284
  onNodeSelect,
27168
- defaultExpanded: true
27169
- },
27170
- item.id
27171
- )) });
27285
+ onNodeAction,
27286
+ nodeActionIcon,
27287
+ nodeActionLabel,
27288
+ className,
27289
+ indent
27290
+ }
27291
+ );
27172
27292
  }
27173
27293
  if (!tree || tree.length === 0) return null;
27174
27294
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
@@ -32569,6 +32689,33 @@ var init_Lightbox = __esm({
32569
32689
  Lightbox.displayName = "Lightbox";
32570
32690
  }
32571
32691
  });
32692
+
32693
+ // lib/relationLabel.ts
32694
+ function relationLabel(value) {
32695
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
32696
+ return null;
32697
+ for (const key of ["name", "title", "label"]) {
32698
+ const candidate = value[key];
32699
+ if (typeof candidate === "string" && candidate !== "") return candidate;
32700
+ }
32701
+ const id = value.id;
32702
+ return id !== void 0 && id !== null ? String(id) : null;
32703
+ }
32704
+ function relationDisplayLabels(value, options) {
32705
+ if (value === void 0 || value === null || value === "") return [];
32706
+ if (Array.isArray(value)) {
32707
+ return value.flatMap((item) => relationDisplayLabels(item, options));
32708
+ }
32709
+ const hydrated = relationLabel(value);
32710
+ if (hydrated !== null) return [hydrated];
32711
+ const raw = String(value);
32712
+ const match = options?.find((opt) => opt.value === raw);
32713
+ return [match ? match.label : raw];
32714
+ }
32715
+ var init_relationLabel = __esm({
32716
+ "lib/relationLabel.ts"() {
32717
+ }
32718
+ });
32572
32719
  function renderIconInput3(icon, props) {
32573
32720
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
32574
32721
  }
@@ -32886,6 +33033,7 @@ var init_TableView = __esm({
32886
33033
  "use client";
32887
33034
  init_cn();
32888
33035
  init_format();
33036
+ init_relationLabel();
32889
33037
  init_getNestedValue();
32890
33038
  init_useEventBus();
32891
33039
  init_Box();
@@ -32899,7 +33047,13 @@ var init_TableView = __esm({
32899
33047
  init_Menu();
32900
33048
  init_useDataDnd();
32901
33049
  tableViewLog = createLogger("almadar:ui:table-view");
32902
- formatCell = (value, format) => formatValue(value, format);
33050
+ formatCell = (value, format) => {
33051
+ if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
33052
+ const labels = relationDisplayLabels(value);
33053
+ if (labels.length > 0) return labels.join(", ");
33054
+ }
33055
+ return formatValue(value, format);
33056
+ };
32903
33057
  MAX_MEASURED_COL_CH = 32;
32904
33058
  BADGE_CHROME_CH = 4;
32905
33059
  alignClass = {
@@ -36376,6 +36530,35 @@ var init_RichTextEditor = __esm({
36376
36530
  document.execCommand("createLink", false, safe);
36377
36531
  afterEdit();
36378
36532
  }, [afterEdit, t, toolbar.link]);
36533
+ const imageInputRef = useRef(null);
36534
+ const savedRangeRef = useRef(null);
36535
+ const execImage = useCallback(() => {
36536
+ const root = ref.current;
36537
+ const sel = window.getSelection();
36538
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
36539
+ imageInputRef.current?.click();
36540
+ }, []);
36541
+ const handleImageFile = useCallback((e) => {
36542
+ const file = e.target.files?.[0];
36543
+ e.target.value = "";
36544
+ if (!file) return;
36545
+ const reader = new FileReader();
36546
+ reader.onload = () => {
36547
+ const url = typeof reader.result === "string" ? reader.result : "";
36548
+ if (!url.startsWith("data:image/")) return;
36549
+ const root = ref.current;
36550
+ if (!root) return;
36551
+ root.focus();
36552
+ const sel = window.getSelection();
36553
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
36554
+ sel.removeAllRanges();
36555
+ sel.addRange(savedRangeRef.current);
36556
+ }
36557
+ document.execCommand("insertImage", false, url);
36558
+ afterEdit();
36559
+ };
36560
+ reader.readAsDataURL(file);
36561
+ }, [afterEdit]);
36379
36562
  const execRule = useCallback(() => {
36380
36563
  document.execCommand("insertHorizontalRule", false);
36381
36564
  afterEdit();
@@ -36388,6 +36571,18 @@ var init_RichTextEditor = __esm({
36388
36571
  }
36389
36572
  return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
36390
36573
  /* @__PURE__ */ jsx(RichTextStyles, {}),
36574
+ /* @__PURE__ */ jsx(
36575
+ "input",
36576
+ {
36577
+ ref: imageInputRef,
36578
+ type: "file",
36579
+ accept: "image/*",
36580
+ className: "hidden",
36581
+ "aria-hidden": "true",
36582
+ tabIndex: -1,
36583
+ onChange: handleImageFile
36584
+ }
36585
+ ),
36391
36586
  showToolbar && /* @__PURE__ */ jsxs(
36392
36587
  Box,
36393
36588
  {
@@ -36413,6 +36608,7 @@ var init_RichTextEditor = __esm({
36413
36608
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
36414
36609
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
36415
36610
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Link$1, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
36611
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Image$1, label: t("richTextEditor.image"), onExec: execImage }),
36416
36612
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Minus, label: t("richTextEditor.divider"), onExec: execRule })
36417
36613
  ]
36418
36614
  }
@@ -36453,6 +36649,8 @@ function DocumentPanel({
36453
36649
  id,
36454
36650
  title,
36455
36651
  subtitle,
36652
+ coverImage,
36653
+ icon,
36456
36654
  value,
36457
36655
  actions,
36458
36656
  maxInlineActions,
@@ -36528,62 +36726,74 @@ function DocumentPanel({
36528
36726
  const emitWithId = (event) => () => {
36529
36727
  if (event) eventBus.emit(`UI:${event}`, { id: recordId });
36530
36728
  };
36531
- return /* @__PURE__ */ jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
36532
- /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
36533
- /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
36534
- titleNode,
36535
- subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
36536
- ] }),
36537
- /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
36538
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
36539
- /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
36540
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
36541
- editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
36542
- /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
36543
- editLabel ?? (t("documentPanel.edit") || "Edit")
36544
- ] }),
36545
- inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
36546
- Button,
36547
- {
36548
- variant: "primary",
36549
- size: "sm",
36550
- onClick: () => fireAction(action),
36551
- "data-testid": `action-${action.event}`,
36552
- children: [
36553
- action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
36554
- action.label
36555
- ]
36556
- },
36557
- idx
36558
- )),
36559
- menuActions.length > 0 && /* @__PURE__ */ jsx(
36560
- Menu,
36561
- {
36562
- position: "bottom-end",
36563
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
36564
- items: menuActions.map((action) => ({
36565
- label: action.label,
36566
- icon: action.icon,
36567
- variant: action.variant === "danger" ? "danger" : "default",
36568
- onClick: () => fireAction(action)
36569
- }))
36570
- }
36571
- )
36572
- ] }) })
36573
- ] }),
36574
- editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
36575
- Box,
36729
+ return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: cn("w-full overflow-hidden", className), children: [
36730
+ coverImage ? /* @__PURE__ */ jsx(Box, { className: "h-44 w-full sm:h-52", "data-testid": "document-cover", children: /* @__PURE__ */ jsx(
36731
+ "img",
36576
36732
  {
36577
- onClick: emitWithId(editEvent),
36578
- className: cn(
36579
- "rounded-md px-1 -mx-1 min-h-[16rem]",
36580
- editEvent && "cursor-text transition-colors hover:bg-muted/30"
36581
- ),
36582
- "data-testid": "document-body",
36583
- children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
36584
- }
36585
- )
36586
- ] }) });
36733
+ src: coverImage,
36734
+ alt: "",
36735
+ "aria-hidden": "true",
36736
+ className: "h-full w-full object-cover"
36737
+ }
36738
+ ) }) : null,
36739
+ /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
36740
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
36741
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
36742
+ icon ? /* @__PURE__ */ jsx(Box, { className: "text-muted-foreground", "data-testid": "document-icon", children: renderIconInput4(icon, { size: "lg" }) }) : null,
36743
+ titleNode,
36744
+ subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
36745
+ ] }),
36746
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
36747
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
36748
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
36749
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
36750
+ editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
36751
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
36752
+ editLabel ?? (t("documentPanel.edit") || "Edit")
36753
+ ] }),
36754
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
36755
+ Button,
36756
+ {
36757
+ variant: "primary",
36758
+ size: "sm",
36759
+ onClick: () => fireAction(action),
36760
+ "data-testid": `action-${action.event}`,
36761
+ children: [
36762
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
36763
+ action.label
36764
+ ]
36765
+ },
36766
+ idx
36767
+ )),
36768
+ menuActions.length > 0 && /* @__PURE__ */ jsx(
36769
+ Menu,
36770
+ {
36771
+ position: "bottom-end",
36772
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
36773
+ items: menuActions.map((action) => ({
36774
+ label: action.label,
36775
+ icon: action.icon,
36776
+ variant: action.variant === "danger" ? "danger" : "default",
36777
+ onClick: () => fireAction(action)
36778
+ }))
36779
+ }
36780
+ )
36781
+ ] }) })
36782
+ ] }),
36783
+ editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
36784
+ Box,
36785
+ {
36786
+ onClick: emitWithId(editEvent),
36787
+ className: cn(
36788
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
36789
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
36790
+ ),
36791
+ "data-testid": "document-body",
36792
+ children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
36793
+ }
36794
+ )
36795
+ ] })
36796
+ ] });
36587
36797
  }
36588
36798
  var init_DocumentPanel = __esm({
36589
36799
  "components/core/molecules/DocumentPanel.tsx"() {
@@ -36607,17 +36817,16 @@ function renderIconInput5(icon, props) {
36607
36817
  function fieldName(field) {
36608
36818
  return field.name ?? field.key ?? "";
36609
36819
  }
36610
- function relationLabel(value) {
36611
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
36612
- for (const key of ["name", "title", "label"]) {
36613
- const candidate = value[key];
36614
- if (typeof candidate === "string" && candidate !== "") return candidate;
36820
+ function relationId(value) {
36821
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
36822
+ const id = value.id;
36823
+ if (id !== void 0 && id !== null) return String(id);
36615
36824
  }
36616
- const id = value.id;
36617
- return id !== void 0 && id !== null ? String(id) : null;
36825
+ return String(value);
36618
36826
  }
36619
36827
  function fieldKind(field, value) {
36620
36828
  if (field.kind) return field.kind;
36829
+ if (field.type === "image") return "image";
36621
36830
  if (field.options && field.options.length > 0) return "select";
36622
36831
  if (typeof value === "boolean" || field.format === "boolean") return "boolean";
36623
36832
  if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
@@ -36627,6 +36836,7 @@ function DocumentDetails({
36627
36836
  entity,
36628
36837
  fields,
36629
36838
  metaCommitEvent,
36839
+ relationEvent,
36630
36840
  title,
36631
36841
  className
36632
36842
  }) {
@@ -36641,11 +36851,51 @@ function DocumentDetails({
36641
36851
  if (!metaCommitEvent || next === current) return;
36642
36852
  eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
36643
36853
  };
36854
+ const relationChip = (item, name, key) => {
36855
+ const label = relationLabel(item) ?? humanizeEnumValue(String(item));
36856
+ if (!relationEvent) {
36857
+ return /* @__PURE__ */ jsx(Badge, { variant: "default", children: label }, key);
36858
+ }
36859
+ const emitClick = () => eventBus.emit(`UI:${relationEvent}`, { field: name, id: relationId(item), name: label });
36860
+ return /* @__PURE__ */ jsx(
36861
+ Badge,
36862
+ {
36863
+ variant: "default",
36864
+ role: "button",
36865
+ tabIndex: 0,
36866
+ className: "cursor-pointer transition-colors hover:bg-accent",
36867
+ onClick: emitClick,
36868
+ onKeyDown: (e) => {
36869
+ if (e.key === "Enter" || e.key === " ") {
36870
+ e.preventDefault();
36871
+ emitClick();
36872
+ }
36873
+ },
36874
+ "data-testid": `relation-chip-${name}`,
36875
+ children: label
36876
+ },
36877
+ key
36878
+ );
36879
+ };
36644
36880
  const renderValue = (field) => {
36645
36881
  const name = fieldName(field);
36646
36882
  const raw = getNestedValue(entity ?? {}, name);
36647
36883
  const kind = fieldKind(field, raw);
36648
36884
  const label = field.label ?? field.header ?? humanizeFieldName(name);
36885
+ if (kind === "image") {
36886
+ const url = typeof raw === "string" ? raw : "";
36887
+ if (!url) return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
36888
+ return /* @__PURE__ */ jsx(
36889
+ "img",
36890
+ {
36891
+ src: url,
36892
+ alt: label,
36893
+ loading: "lazy",
36894
+ className: "max-h-32 w-full rounded-md border border-border object-cover",
36895
+ "data-testid": `document-property-image-${name}`
36896
+ }
36897
+ );
36898
+ }
36649
36899
  if (kind === "boolean") {
36650
36900
  return /* @__PURE__ */ jsx(
36651
36901
  Switch,
@@ -36675,9 +36925,12 @@ function DocumentDetails({
36675
36925
  if (raw.length === 0) {
36676
36926
  return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
36677
36927
  }
36678
- return /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
36928
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => relationChip(item, name, i)) });
36679
36929
  }
36680
- const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
36930
+ if (raw !== void 0 && raw !== null && raw !== "" && relationLabel(raw) !== null) {
36931
+ return relationChip(raw, name);
36932
+ }
36933
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : formatValue(raw, field.format);
36681
36934
  return /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
36682
36935
  }
36683
36936
  if (fieldDraft?.name === name) {
@@ -36746,6 +36999,7 @@ var init_DocumentDetails = __esm({
36746
36999
  init_cn();
36747
37000
  init_format();
36748
37001
  init_getNestedValue();
37002
+ init_relationLabel();
36749
37003
  init_useEventBus();
36750
37004
  init_Box();
36751
37005
  init_Stack();
@@ -41740,7 +41994,18 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41740
41994
  if (value === void 0 || value === null) return "\u2014";
41741
41995
  const str = String(value);
41742
41996
  switch (fieldType) {
41743
- case "image":
41997
+ case "image": {
41998
+ if (!str) return "\u2014";
41999
+ return /* @__PURE__ */ jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsx(
42000
+ "img",
42001
+ {
42002
+ src: str,
42003
+ alt: formatFieldLabel(fieldName2),
42004
+ className: "max-w-full max-h-64 rounded-md object-contain",
42005
+ loading: "lazy"
42006
+ }
42007
+ ) });
42008
+ }
41744
42009
  case "url": {
41745
42010
  if (str.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str.startsWith("data:image/")) {
41746
42011
  return /* @__PURE__ */ jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsx(
@@ -41753,7 +42018,7 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41753
42018
  }
41754
42019
  ) });
41755
42020
  }
41756
- if (fieldType === "url" && /^https?:\/\//i.test(str)) {
42021
+ if (/^https?:\/\//i.test(str)) {
41757
42022
  return /* @__PURE__ */ jsx(
41758
42023
  "a",
41759
42024
  {
@@ -41839,8 +42104,10 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41839
42104
  return str;
41840
42105
  }
41841
42106
  case "relation": {
41842
- const match = meta?.options?.find((opt) => opt.value === str);
41843
- return match ? match.label : str;
42107
+ const labels = relationDisplayLabels(value, meta?.options);
42108
+ if (labels.length === 0) return "\u2014";
42109
+ if (labels.length === 1) return labels[0];
42110
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", wrap: true, children: labels.map((label, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: label }, i)) });
41844
42111
  }
41845
42112
  case "file": {
41846
42113
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -41942,6 +42209,7 @@ var init_DetailPanel = __esm({
41942
42209
  init_cn();
41943
42210
  init_format();
41944
42211
  init_getNestedValue();
42212
+ init_relationLabel();
41945
42213
  init_useEventBus();
41946
42214
  init_UploadDropZone();
41947
42215
  formatFieldLabel = humanizeFieldName;
@@ -42600,6 +42868,8 @@ function determineInputType(field) {
42600
42868
  return "url";
42601
42869
  case "file":
42602
42870
  return "file";
42871
+ case "image":
42872
+ return "image";
42603
42873
  case "money":
42604
42874
  return "currency";
42605
42875
  case "number":
@@ -43199,6 +43469,50 @@ var init_Form = __esm({
43199
43469
  }
43200
43470
  }
43201
43471
  );
43472
+ case "image": {
43473
+ const imageUrl = typeof currentValue === "string" ? currentValue : "";
43474
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
43475
+ imageUrl ? /* @__PURE__ */ jsx(
43476
+ "img",
43477
+ {
43478
+ src: imageUrl,
43479
+ alt: "",
43480
+ "aria-hidden": "true",
43481
+ className: "h-24 w-full max-w-xs rounded-md border border-border object-cover",
43482
+ "data-testid": `image-preview-${fieldName2}`
43483
+ }
43484
+ ) : null,
43485
+ /* @__PURE__ */ jsx(
43486
+ UploadDropZone,
43487
+ {
43488
+ accept: "image/*",
43489
+ maxFiles: 1,
43490
+ maxSize: 2 * 1024 * 1024,
43491
+ disabled: isLoading,
43492
+ onFiles: (files) => {
43493
+ const f3 = files[0];
43494
+ if (!f3) return;
43495
+ const reader = new FileReader();
43496
+ reader.onload = () => handleChange(
43497
+ fieldName2,
43498
+ typeof reader.result === "string" ? reader.result : ""
43499
+ );
43500
+ reader.readAsDataURL(f3);
43501
+ }
43502
+ }
43503
+ ),
43504
+ /* @__PURE__ */ jsx(
43505
+ Input,
43506
+ {
43507
+ ...commonProps,
43508
+ type: "url",
43509
+ placeholder: t("form.imageUrlFallback"),
43510
+ value: imageUrl,
43511
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43512
+ }
43513
+ )
43514
+ ] });
43515
+ }
43202
43516
  case "date":
43203
43517
  return /* @__PURE__ */ jsx(
43204
43518
  Input,