@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.
@@ -22562,6 +22562,7 @@ var init_DashboardLayout = __esm({
22562
22562
  onNotificationClick,
22563
22563
  showThemeToggle = true,
22564
22564
  sidebarFooter,
22565
+ sidebarContent,
22565
22566
  onSignOut: onSignOutProp,
22566
22567
  currentPath,
22567
22568
  layoutMode = "sidebar",
@@ -22691,7 +22692,11 @@ var init_DashboardLayout = __esm({
22691
22692
  {
22692
22693
  as: "nav",
22693
22694
  gap: "none",
22694
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
22695
+ className: cn(
22696
+ "py-4 space-y-1 overflow-y-auto",
22697
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
22698
+ isRail ? "px-2" : "px-3"
22699
+ ),
22695
22700
  children: navItems.map((item) => /* @__PURE__ */ jsx(
22696
22701
  NavLink,
22697
22702
  {
@@ -22703,6 +22708,7 @@ var init_DashboardLayout = __esm({
22703
22708
  ))
22704
22709
  }
22705
22710
  ),
22711
+ sidebarContent && !isRail && /* @__PURE__ */ jsx(Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
22706
22712
  sidebarFooter && /* @__PURE__ */ jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
22707
22713
  ]
22708
22714
  }
@@ -25773,7 +25779,17 @@ function fileIcon(name) {
25773
25779
  return "file";
25774
25780
  }
25775
25781
  }
25776
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
25782
+ function ancestorsOf(id, byId) {
25783
+ const out = /* @__PURE__ */ new Set();
25784
+ if (!id) return out;
25785
+ let cur = byId.get(id)?.parentId;
25786
+ while (cur && byId.has(cur) && !out.has(cur)) {
25787
+ out.add(cur);
25788
+ cur = byId.get(cur)?.parentId;
25789
+ }
25790
+ return out;
25791
+ }
25792
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
25777
25793
  var init_FileTree = __esm({
25778
25794
  "components/core/molecules/FileTree.tsx"() {
25779
25795
  "use client";
@@ -25864,42 +25880,77 @@ var init_FileTree = __esm({
25864
25880
  indent,
25865
25881
  childrenByParent,
25866
25882
  onNodeSelect,
25867
- defaultExpanded = false
25883
+ onNodeAction,
25884
+ nodeActionIcon,
25885
+ nodeActionLabel,
25886
+ selectedId,
25887
+ look,
25888
+ isExpanded,
25889
+ onToggle
25868
25890
  }) => {
25869
- const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
25870
25891
  const children = childrenByParent.get(item.id);
25871
25892
  const hasChildren = !!children && children.length > 0;
25893
+ const expanded = hasChildren && isExpanded(item.id, depth);
25894
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
25895
+ const nav = look === "nav";
25872
25896
  const handleClick = useCallback(() => {
25873
- if (hasChildren) setExpanded((prev) => !prev);
25897
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
25874
25898
  onNodeSelect?.(item.id);
25875
- }, [hasChildren, item.id, onNodeSelect]);
25899
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
25900
+ const handleChevron = useCallback((e) => {
25901
+ e.stopPropagation();
25902
+ onToggle(item.id, expanded);
25903
+ }, [item.id, expanded, onToggle]);
25904
+ const handleAction = useCallback((e) => {
25905
+ e.stopPropagation();
25906
+ onNodeAction?.(item.id);
25907
+ }, [item.id, onNodeAction]);
25876
25908
  return /* @__PURE__ */ jsxs(Fragment, { children: [
25877
25909
  /* @__PURE__ */ jsxs(
25878
25910
  Box,
25879
25911
  {
25880
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
25912
+ 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"}`,
25881
25913
  style: { paddingLeft: depth * indent + 8 },
25882
25914
  onClick: handleClick,
25883
25915
  role: "treeitem",
25916
+ "aria-selected": isSelected,
25884
25917
  "aria-expanded": hasChildren ? expanded : void 0,
25885
25918
  children: [
25886
- hasChildren ? /* @__PURE__ */ jsx(
25919
+ hasChildren ? /* @__PURE__ */ jsx(Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsx(
25887
25920
  Icon,
25888
25921
  {
25889
25922
  name: expanded ? "chevron-down" : "chevron-right",
25890
25923
  size: "xs",
25891
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
25924
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
25892
25925
  }
25893
- ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
25926
+ ) }) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
25894
25927
  /* @__PURE__ */ jsx(
25895
25928
  Icon,
25896
25929
  {
25897
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25930
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25898
25931
  size: "xs",
25899
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
25932
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
25900
25933
  }
25901
25934
  ),
25902
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
25935
+ /* @__PURE__ */ jsx(
25936
+ Typography,
25937
+ {
25938
+ variant: "caption",
25939
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
25940
+ children: item.label
25941
+ }
25942
+ ),
25943
+ onNodeAction && /* @__PURE__ */ jsx(
25944
+ Box,
25945
+ {
25946
+ onClick: handleAction,
25947
+ role: "button",
25948
+ "aria-label": nodeActionLabel ?? "Node action",
25949
+ title: nodeActionLabel,
25950
+ 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"}`,
25951
+ children: /* @__PURE__ */ jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
25952
+ }
25953
+ )
25903
25954
  ]
25904
25955
  }
25905
25956
  ),
@@ -25910,47 +25961,116 @@ var init_FileTree = __esm({
25910
25961
  depth: depth + 1,
25911
25962
  indent,
25912
25963
  childrenByParent,
25913
- onNodeSelect
25964
+ onNodeSelect,
25965
+ onNodeAction,
25966
+ nodeActionIcon,
25967
+ nodeActionLabel,
25968
+ selectedId,
25969
+ look,
25970
+ isExpanded,
25971
+ onToggle
25914
25972
  },
25915
25973
  child.id
25916
25974
  )) })
25917
25975
  ] });
25918
25976
  };
25977
+ FlatFileTree = ({
25978
+ items,
25979
+ selectedId,
25980
+ look = "files",
25981
+ onNodeSelect,
25982
+ onNodeAction,
25983
+ nodeActionIcon,
25984
+ nodeActionLabel,
25985
+ className,
25986
+ indent = 16
25987
+ }) => {
25988
+ const [overrides, setOverrides] = useState(() => /* @__PURE__ */ new Map());
25989
+ const byId = React77__default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
25990
+ const selectedAncestors = React77__default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
25991
+ const lastSelectedRef = React77__default.useRef(selectedId);
25992
+ React77__default.useEffect(() => {
25993
+ if (selectedId === lastSelectedRef.current) return;
25994
+ lastSelectedRef.current = selectedId;
25995
+ setOverrides((prev) => {
25996
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
25997
+ const next = new Map(prev);
25998
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
25999
+ return next;
26000
+ });
26001
+ }, [selectedId, selectedAncestors]);
26002
+ const isExpanded = useCallback(
26003
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
26004
+ [overrides, selectedAncestors]
26005
+ );
26006
+ const onToggle = useCallback((id, effective) => {
26007
+ setOverrides((prev) => {
26008
+ const next = new Map(prev);
26009
+ next.set(id, !effective);
26010
+ return next;
26011
+ });
26012
+ }, []);
26013
+ if (items.length === 0) return null;
26014
+ const ids = new Set(items.map((node) => node.id));
26015
+ const childrenByParent = /* @__PURE__ */ new Map();
26016
+ const roots = [];
26017
+ for (const item of items) {
26018
+ if (item.parentId && ids.has(item.parentId)) {
26019
+ const siblings = childrenByParent.get(item.parentId);
26020
+ if (siblings) siblings.push(item);
26021
+ else childrenByParent.set(item.parentId, [item]);
26022
+ } else {
26023
+ roots.push(item);
26024
+ }
26025
+ }
26026
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
26027
+ FlatTreeNodeItem,
26028
+ {
26029
+ item,
26030
+ depth: 0,
26031
+ indent,
26032
+ childrenByParent,
26033
+ onNodeSelect,
26034
+ onNodeAction,
26035
+ nodeActionIcon,
26036
+ nodeActionLabel,
26037
+ selectedId,
26038
+ look,
26039
+ isExpanded,
26040
+ onToggle
26041
+ },
26042
+ item.id
26043
+ )) });
26044
+ };
25919
26045
  FileTree = ({
25920
26046
  tree,
25921
26047
  items,
25922
26048
  selectedPath,
26049
+ selectedId,
26050
+ look = "files",
25923
26051
  onFileSelect,
25924
26052
  onNodeSelect,
26053
+ onNodeAction,
26054
+ nodeActionIcon,
26055
+ nodeActionLabel,
25925
26056
  className,
25926
26057
  indent = 16
25927
26058
  }) => {
25928
26059
  if (items) {
25929
- if (items.length === 0) return null;
25930
- const ids = new Set(items.map((node) => node.id));
25931
- const childrenByParent = /* @__PURE__ */ new Map();
25932
- const roots = [];
25933
- for (const item of items) {
25934
- if (item.parentId && ids.has(item.parentId)) {
25935
- const siblings = childrenByParent.get(item.parentId);
25936
- if (siblings) siblings.push(item);
25937
- else childrenByParent.set(item.parentId, [item]);
25938
- } else {
25939
- roots.push(item);
25940
- }
25941
- }
25942
- return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
25943
- FlatTreeNodeItem,
26060
+ return /* @__PURE__ */ jsx(
26061
+ FlatFileTree,
25944
26062
  {
25945
- item,
25946
- depth: 0,
25947
- indent,
25948
- childrenByParent,
26063
+ items,
26064
+ selectedId,
26065
+ look,
25949
26066
  onNodeSelect,
25950
- defaultExpanded: true
25951
- },
25952
- item.id
25953
- )) });
26067
+ onNodeAction,
26068
+ nodeActionIcon,
26069
+ nodeActionLabel,
26070
+ className,
26071
+ indent
26072
+ }
26073
+ );
25954
26074
  }
25955
26075
  if (!tree || tree.length === 0) return null;
25956
26076
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
@@ -34105,6 +34225,33 @@ var init_Lightbox = __esm({
34105
34225
  Lightbox.displayName = "Lightbox";
34106
34226
  }
34107
34227
  });
34228
+
34229
+ // lib/relationLabel.ts
34230
+ function relationLabel(value) {
34231
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
34232
+ return null;
34233
+ for (const key of ["name", "title", "label"]) {
34234
+ const candidate = value[key];
34235
+ if (typeof candidate === "string" && candidate !== "") return candidate;
34236
+ }
34237
+ const id = value.id;
34238
+ return id !== void 0 && id !== null ? String(id) : null;
34239
+ }
34240
+ function relationDisplayLabels(value, options) {
34241
+ if (value === void 0 || value === null || value === "") return [];
34242
+ if (Array.isArray(value)) {
34243
+ return value.flatMap((item) => relationDisplayLabels(item, options));
34244
+ }
34245
+ const hydrated = relationLabel(value);
34246
+ if (hydrated !== null) return [hydrated];
34247
+ const raw = String(value);
34248
+ const match = options?.find((opt) => opt.value === raw);
34249
+ return [match ? match.label : raw];
34250
+ }
34251
+ var init_relationLabel = __esm({
34252
+ "lib/relationLabel.ts"() {
34253
+ }
34254
+ });
34108
34255
  function renderIconInput3(icon, props) {
34109
34256
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
34110
34257
  }
@@ -34422,6 +34569,7 @@ var init_TableView = __esm({
34422
34569
  "use client";
34423
34570
  init_cn();
34424
34571
  init_format();
34572
+ init_relationLabel();
34425
34573
  init_getNestedValue();
34426
34574
  init_useEventBus();
34427
34575
  init_Box();
@@ -34435,7 +34583,13 @@ var init_TableView = __esm({
34435
34583
  init_Menu();
34436
34584
  init_useDataDnd();
34437
34585
  tableViewLog = createLogger("almadar:ui:table-view");
34438
- formatCell = (value, format) => formatValue(value, format);
34586
+ formatCell = (value, format) => {
34587
+ if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
34588
+ const labels = relationDisplayLabels(value);
34589
+ if (labels.length > 0) return labels.join(", ");
34590
+ }
34591
+ return formatValue(value, format);
34592
+ };
34439
34593
  MAX_MEASURED_COL_CH = 32;
34440
34594
  BADGE_CHROME_CH = 4;
34441
34595
  alignClass = {
@@ -38050,6 +38204,35 @@ var init_RichTextEditor = __esm({
38050
38204
  document.execCommand("createLink", false, safe);
38051
38205
  afterEdit();
38052
38206
  }, [afterEdit, t, toolbar.link]);
38207
+ const imageInputRef = useRef(null);
38208
+ const savedRangeRef = useRef(null);
38209
+ const execImage = useCallback(() => {
38210
+ const root = ref.current;
38211
+ const sel = window.getSelection();
38212
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
38213
+ imageInputRef.current?.click();
38214
+ }, []);
38215
+ const handleImageFile = useCallback((e) => {
38216
+ const file = e.target.files?.[0];
38217
+ e.target.value = "";
38218
+ if (!file) return;
38219
+ const reader = new FileReader();
38220
+ reader.onload = () => {
38221
+ const url = typeof reader.result === "string" ? reader.result : "";
38222
+ if (!url.startsWith("data:image/")) return;
38223
+ const root = ref.current;
38224
+ if (!root) return;
38225
+ root.focus();
38226
+ const sel = window.getSelection();
38227
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
38228
+ sel.removeAllRanges();
38229
+ sel.addRange(savedRangeRef.current);
38230
+ }
38231
+ document.execCommand("insertImage", false, url);
38232
+ afterEdit();
38233
+ };
38234
+ reader.readAsDataURL(file);
38235
+ }, [afterEdit]);
38053
38236
  const execRule = useCallback(() => {
38054
38237
  document.execCommand("insertHorizontalRule", false);
38055
38238
  afterEdit();
@@ -38062,6 +38245,18 @@ var init_RichTextEditor = __esm({
38062
38245
  }
38063
38246
  return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
38064
38247
  /* @__PURE__ */ jsx(RichTextStyles, {}),
38248
+ /* @__PURE__ */ jsx(
38249
+ "input",
38250
+ {
38251
+ ref: imageInputRef,
38252
+ type: "file",
38253
+ accept: "image/*",
38254
+ className: "hidden",
38255
+ "aria-hidden": "true",
38256
+ tabIndex: -1,
38257
+ onChange: handleImageFile
38258
+ }
38259
+ ),
38065
38260
  showToolbar && /* @__PURE__ */ jsxs(
38066
38261
  Box,
38067
38262
  {
@@ -38087,6 +38282,7 @@ var init_RichTextEditor = __esm({
38087
38282
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
38088
38283
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
38089
38284
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Link$1, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
38285
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Image$1, label: t("richTextEditor.image"), onExec: execImage }),
38090
38286
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Minus, label: t("richTextEditor.divider"), onExec: execRule })
38091
38287
  ]
38092
38288
  }
@@ -38127,6 +38323,8 @@ function DocumentPanel({
38127
38323
  id,
38128
38324
  title,
38129
38325
  subtitle,
38326
+ coverImage,
38327
+ icon,
38130
38328
  value,
38131
38329
  actions,
38132
38330
  maxInlineActions,
@@ -38202,62 +38400,74 @@ function DocumentPanel({
38202
38400
  const emitWithId = (event) => () => {
38203
38401
  if (event) eventBus.emit(`UI:${event}`, { id: recordId });
38204
38402
  };
38205
- return /* @__PURE__ */ jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38206
- /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
38207
- /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38208
- titleNode,
38209
- subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38210
- ] }),
38211
- /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
38212
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38213
- /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38214
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
38215
- editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38216
- /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
38217
- editLabel ?? (t("documentPanel.edit") || "Edit")
38218
- ] }),
38219
- inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
38220
- Button,
38221
- {
38222
- variant: "primary",
38223
- size: "sm",
38224
- onClick: () => fireAction(action),
38225
- "data-testid": `action-${action.event}`,
38226
- children: [
38227
- action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38228
- action.label
38229
- ]
38230
- },
38231
- idx
38232
- )),
38233
- menuActions.length > 0 && /* @__PURE__ */ jsx(
38234
- Menu,
38235
- {
38236
- position: "bottom-end",
38237
- 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" }) }),
38238
- items: menuActions.map((action) => ({
38239
- label: action.label,
38240
- icon: action.icon,
38241
- variant: action.variant === "danger" ? "danger" : "default",
38242
- onClick: () => fireAction(action)
38243
- }))
38244
- }
38245
- )
38246
- ] }) })
38247
- ] }),
38248
- editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
38249
- Box,
38403
+ return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: cn("w-full overflow-hidden", className), children: [
38404
+ coverImage ? /* @__PURE__ */ jsx(Box, { className: "h-44 w-full sm:h-52", "data-testid": "document-cover", children: /* @__PURE__ */ jsx(
38405
+ "img",
38250
38406
  {
38251
- onClick: emitWithId(editEvent),
38252
- className: cn(
38253
- "rounded-md px-1 -mx-1 min-h-[16rem]",
38254
- editEvent && "cursor-text transition-colors hover:bg-muted/30"
38255
- ),
38256
- "data-testid": "document-body",
38257
- children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
38258
- }
38259
- )
38260
- ] }) });
38407
+ src: coverImage,
38408
+ alt: "",
38409
+ "aria-hidden": "true",
38410
+ className: "h-full w-full object-cover"
38411
+ }
38412
+ ) }) : null,
38413
+ /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38414
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
38415
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38416
+ icon ? /* @__PURE__ */ jsx(Box, { className: "text-muted-foreground", "data-testid": "document-icon", children: renderIconInput4(icon, { size: "lg" }) }) : null,
38417
+ titleNode,
38418
+ subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38419
+ ] }),
38420
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
38421
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38422
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38423
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
38424
+ editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38425
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
38426
+ editLabel ?? (t("documentPanel.edit") || "Edit")
38427
+ ] }),
38428
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
38429
+ Button,
38430
+ {
38431
+ variant: "primary",
38432
+ size: "sm",
38433
+ onClick: () => fireAction(action),
38434
+ "data-testid": `action-${action.event}`,
38435
+ children: [
38436
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38437
+ action.label
38438
+ ]
38439
+ },
38440
+ idx
38441
+ )),
38442
+ menuActions.length > 0 && /* @__PURE__ */ jsx(
38443
+ Menu,
38444
+ {
38445
+ position: "bottom-end",
38446
+ 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" }) }),
38447
+ items: menuActions.map((action) => ({
38448
+ label: action.label,
38449
+ icon: action.icon,
38450
+ variant: action.variant === "danger" ? "danger" : "default",
38451
+ onClick: () => fireAction(action)
38452
+ }))
38453
+ }
38454
+ )
38455
+ ] }) })
38456
+ ] }),
38457
+ editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
38458
+ Box,
38459
+ {
38460
+ onClick: emitWithId(editEvent),
38461
+ className: cn(
38462
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
38463
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
38464
+ ),
38465
+ "data-testid": "document-body",
38466
+ children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
38467
+ }
38468
+ )
38469
+ ] })
38470
+ ] });
38261
38471
  }
38262
38472
  var init_DocumentPanel = __esm({
38263
38473
  "components/core/molecules/DocumentPanel.tsx"() {
@@ -38281,17 +38491,16 @@ function renderIconInput5(icon, props) {
38281
38491
  function fieldName(field) {
38282
38492
  return field.name ?? field.key ?? "";
38283
38493
  }
38284
- function relationLabel(value) {
38285
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
38286
- for (const key of ["name", "title", "label"]) {
38287
- const candidate = value[key];
38288
- if (typeof candidate === "string" && candidate !== "") return candidate;
38494
+ function relationId(value) {
38495
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
38496
+ const id = value.id;
38497
+ if (id !== void 0 && id !== null) return String(id);
38289
38498
  }
38290
- const id = value.id;
38291
- return id !== void 0 && id !== null ? String(id) : null;
38499
+ return String(value);
38292
38500
  }
38293
38501
  function fieldKind(field, value) {
38294
38502
  if (field.kind) return field.kind;
38503
+ if (field.type === "image") return "image";
38295
38504
  if (field.options && field.options.length > 0) return "select";
38296
38505
  if (typeof value === "boolean" || field.format === "boolean") return "boolean";
38297
38506
  if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
@@ -38301,6 +38510,7 @@ function DocumentDetails({
38301
38510
  entity,
38302
38511
  fields,
38303
38512
  metaCommitEvent,
38513
+ relationEvent,
38304
38514
  title,
38305
38515
  className
38306
38516
  }) {
@@ -38315,11 +38525,51 @@ function DocumentDetails({
38315
38525
  if (!metaCommitEvent || next === current) return;
38316
38526
  eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
38317
38527
  };
38528
+ const relationChip = (item, name, key) => {
38529
+ const label = relationLabel(item) ?? humanizeEnumValue(String(item));
38530
+ if (!relationEvent) {
38531
+ return /* @__PURE__ */ jsx(Badge, { variant: "default", children: label }, key);
38532
+ }
38533
+ const emitClick = () => eventBus.emit(`UI:${relationEvent}`, { field: name, id: relationId(item), name: label });
38534
+ return /* @__PURE__ */ jsx(
38535
+ Badge,
38536
+ {
38537
+ variant: "default",
38538
+ role: "button",
38539
+ tabIndex: 0,
38540
+ className: "cursor-pointer transition-colors hover:bg-accent",
38541
+ onClick: emitClick,
38542
+ onKeyDown: (e) => {
38543
+ if (e.key === "Enter" || e.key === " ") {
38544
+ e.preventDefault();
38545
+ emitClick();
38546
+ }
38547
+ },
38548
+ "data-testid": `relation-chip-${name}`,
38549
+ children: label
38550
+ },
38551
+ key
38552
+ );
38553
+ };
38318
38554
  const renderValue = (field) => {
38319
38555
  const name = fieldName(field);
38320
38556
  const raw = getNestedValue(entity ?? {}, name);
38321
38557
  const kind = fieldKind(field, raw);
38322
38558
  const label = field.label ?? field.header ?? humanizeFieldName(name);
38559
+ if (kind === "image") {
38560
+ const url = typeof raw === "string" ? raw : "";
38561
+ if (!url) return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
38562
+ return /* @__PURE__ */ jsx(
38563
+ "img",
38564
+ {
38565
+ src: url,
38566
+ alt: label,
38567
+ loading: "lazy",
38568
+ className: "max-h-32 w-full rounded-md border border-border object-cover",
38569
+ "data-testid": `document-property-image-${name}`
38570
+ }
38571
+ );
38572
+ }
38323
38573
  if (kind === "boolean") {
38324
38574
  return /* @__PURE__ */ jsx(
38325
38575
  Switch,
@@ -38349,9 +38599,12 @@ function DocumentDetails({
38349
38599
  if (raw.length === 0) {
38350
38600
  return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
38351
38601
  }
38352
- 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)) });
38602
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => relationChip(item, name, i)) });
38353
38603
  }
38354
- const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
38604
+ if (raw !== void 0 && raw !== null && raw !== "" && relationLabel(raw) !== null) {
38605
+ return relationChip(raw, name);
38606
+ }
38607
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : formatValue(raw, field.format);
38355
38608
  return /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
38356
38609
  }
38357
38610
  if (fieldDraft?.name === name) {
@@ -38420,6 +38673,7 @@ var init_DocumentDetails = __esm({
38420
38673
  init_cn();
38421
38674
  init_format();
38422
38675
  init_getNestedValue();
38676
+ init_relationLabel();
38423
38677
  init_useEventBus();
38424
38678
  init_Box();
38425
38679
  init_Stack();
@@ -43641,7 +43895,18 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43641
43895
  if (value === void 0 || value === null) return "\u2014";
43642
43896
  const str2 = String(value);
43643
43897
  switch (fieldType) {
43644
- case "image":
43898
+ case "image": {
43899
+ if (!str2) return "\u2014";
43900
+ return /* @__PURE__ */ jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsx(
43901
+ "img",
43902
+ {
43903
+ src: str2,
43904
+ alt: formatFieldLabel(fieldName2),
43905
+ className: "max-w-full max-h-64 rounded-md object-contain",
43906
+ loading: "lazy"
43907
+ }
43908
+ ) });
43909
+ }
43645
43910
  case "url": {
43646
43911
  if (str2.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str2.startsWith("data:image/")) {
43647
43912
  return /* @__PURE__ */ jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsx(
@@ -43654,7 +43919,7 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43654
43919
  }
43655
43920
  ) });
43656
43921
  }
43657
- if (fieldType === "url" && /^https?:\/\//i.test(str2)) {
43922
+ if (/^https?:\/\//i.test(str2)) {
43658
43923
  return /* @__PURE__ */ jsx(
43659
43924
  "a",
43660
43925
  {
@@ -43740,8 +44005,10 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43740
44005
  return str2;
43741
44006
  }
43742
44007
  case "relation": {
43743
- const match = meta?.options?.find((opt) => opt.value === str2);
43744
- return match ? match.label : str2;
44008
+ const labels = relationDisplayLabels(value, meta?.options);
44009
+ if (labels.length === 0) return "\u2014";
44010
+ if (labels.length === 1) return labels[0];
44011
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", wrap: true, children: labels.map((label, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: label }, i)) });
43745
44012
  }
43746
44013
  case "file": {
43747
44014
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -43843,6 +44110,7 @@ var init_DetailPanel = __esm({
43843
44110
  init_cn();
43844
44111
  init_format();
43845
44112
  init_getNestedValue();
44113
+ init_relationLabel();
43846
44114
  init_useEventBus();
43847
44115
  init_UploadDropZone();
43848
44116
  formatFieldLabel = humanizeFieldName;
@@ -44501,6 +44769,8 @@ function determineInputType(field) {
44501
44769
  return "url";
44502
44770
  case "file":
44503
44771
  return "file";
44772
+ case "image":
44773
+ return "image";
44504
44774
  case "money":
44505
44775
  return "currency";
44506
44776
  case "number":
@@ -45100,6 +45370,50 @@ var init_Form = __esm({
45100
45370
  }
45101
45371
  }
45102
45372
  );
45373
+ case "image": {
45374
+ const imageUrl = typeof currentValue2 === "string" ? currentValue2 : "";
45375
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
45376
+ imageUrl ? /* @__PURE__ */ jsx(
45377
+ "img",
45378
+ {
45379
+ src: imageUrl,
45380
+ alt: "",
45381
+ "aria-hidden": "true",
45382
+ className: "h-24 w-full max-w-xs rounded-md border border-border object-cover",
45383
+ "data-testid": `image-preview-${fieldName2}`
45384
+ }
45385
+ ) : null,
45386
+ /* @__PURE__ */ jsx(
45387
+ UploadDropZone,
45388
+ {
45389
+ accept: "image/*",
45390
+ maxFiles: 1,
45391
+ maxSize: 2 * 1024 * 1024,
45392
+ disabled: isLoading,
45393
+ onFiles: (files) => {
45394
+ const f3 = files[0];
45395
+ if (!f3) return;
45396
+ const reader = new FileReader();
45397
+ reader.onload = () => handleChange(
45398
+ fieldName2,
45399
+ typeof reader.result === "string" ? reader.result : ""
45400
+ );
45401
+ reader.readAsDataURL(f3);
45402
+ }
45403
+ }
45404
+ ),
45405
+ /* @__PURE__ */ jsx(
45406
+ Input,
45407
+ {
45408
+ ...commonProps,
45409
+ type: "url",
45410
+ placeholder: t("form.imageUrlFallback"),
45411
+ value: imageUrl,
45412
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45413
+ }
45414
+ )
45415
+ ] });
45416
+ }
45103
45417
  case "date":
45104
45418
  return /* @__PURE__ */ jsx(
45105
45419
  Input,
@@ -53866,7 +54180,10 @@ var en_default = {
53866
54180
  "richTextEditor.removeLink": "Remove link",
53867
54181
  "richTextEditor.divider": "Divider",
53868
54182
  "richTextEditor.linkPrompt": "Link address",
53869
- "richTextEditor.placeholder": "Start writing\u2026"
54183
+ "richTextEditor.image": "Insert image",
54184
+ "richTextEditor.imagePrompt": "Image URL",
54185
+ "richTextEditor.placeholder": "Start writing\u2026",
54186
+ "form.imageUrlFallback": "\u2026or paste an image address"
53870
54187
  };
53871
54188
 
53872
54189
  // hooks/useTranslate.ts