@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.
package/dist/avl/index.js CHANGED
@@ -27152,6 +27152,7 @@ var init_DashboardLayout = __esm({
27152
27152
  onNotificationClick,
27153
27153
  showThemeToggle = true,
27154
27154
  sidebarFooter,
27155
+ sidebarContent,
27155
27156
  onSignOut: onSignOutProp,
27156
27157
  currentPath,
27157
27158
  layoutMode = "sidebar",
@@ -27277,7 +27278,11 @@ var init_DashboardLayout = __esm({
27277
27278
  {
27278
27279
  as: "nav",
27279
27280
  gap: "none",
27280
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
27281
+ className: cn(
27282
+ "py-4 space-y-1 overflow-y-auto",
27283
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
27284
+ isRail ? "px-2" : "px-3"
27285
+ ),
27281
27286
  children: navItems.map((item) => /* @__PURE__ */ jsx(
27282
27287
  NavLink,
27283
27288
  {
@@ -27289,6 +27294,7 @@ var init_DashboardLayout = __esm({
27289
27294
  ))
27290
27295
  }
27291
27296
  ),
27297
+ sidebarContent && !isRail && /* @__PURE__ */ jsx(Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
27292
27298
  sidebarFooter && /* @__PURE__ */ jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
27293
27299
  ]
27294
27300
  }
@@ -29698,7 +29704,17 @@ function fileIcon(name) {
29698
29704
  return "file";
29699
29705
  }
29700
29706
  }
29701
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
29707
+ function ancestorsOf(id, byId) {
29708
+ const out = /* @__PURE__ */ new Set();
29709
+ if (!id) return out;
29710
+ let cur = byId.get(id)?.parentId;
29711
+ while (cur && byId.has(cur) && !out.has(cur)) {
29712
+ out.add(cur);
29713
+ cur = byId.get(cur)?.parentId;
29714
+ }
29715
+ return out;
29716
+ }
29717
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
29702
29718
  var init_FileTree = __esm({
29703
29719
  "components/core/molecules/FileTree.tsx"() {
29704
29720
  "use client";
@@ -29789,42 +29805,77 @@ var init_FileTree = __esm({
29789
29805
  indent,
29790
29806
  childrenByParent,
29791
29807
  onNodeSelect,
29792
- defaultExpanded = false
29808
+ onNodeAction,
29809
+ nodeActionIcon,
29810
+ nodeActionLabel,
29811
+ selectedId,
29812
+ look,
29813
+ isExpanded,
29814
+ onToggle
29793
29815
  }) => {
29794
- const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
29795
29816
  const children = childrenByParent.get(item.id);
29796
29817
  const hasChildren = !!children && children.length > 0;
29818
+ const expanded = hasChildren && isExpanded(item.id, depth);
29819
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
29820
+ const nav = look === "nav";
29797
29821
  const handleClick = useCallback(() => {
29798
- if (hasChildren) setExpanded((prev) => !prev);
29822
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
29799
29823
  onNodeSelect?.(item.id);
29800
- }, [hasChildren, item.id, onNodeSelect]);
29824
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
29825
+ const handleChevron = useCallback((e) => {
29826
+ e.stopPropagation();
29827
+ onToggle(item.id, expanded);
29828
+ }, [item.id, expanded, onToggle]);
29829
+ const handleAction = useCallback((e) => {
29830
+ e.stopPropagation();
29831
+ onNodeAction?.(item.id);
29832
+ }, [item.id, onNodeAction]);
29801
29833
  return /* @__PURE__ */ jsxs(Fragment, { children: [
29802
29834
  /* @__PURE__ */ jsxs(
29803
29835
  Box,
29804
29836
  {
29805
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
29837
+ 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"}`,
29806
29838
  style: { paddingLeft: depth * indent + 8 },
29807
29839
  onClick: handleClick,
29808
29840
  role: "treeitem",
29841
+ "aria-selected": isSelected,
29809
29842
  "aria-expanded": hasChildren ? expanded : void 0,
29810
29843
  children: [
29811
- hasChildren ? /* @__PURE__ */ jsx(
29844
+ hasChildren ? /* @__PURE__ */ jsx(Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsx(
29812
29845
  Icon,
29813
29846
  {
29814
29847
  name: expanded ? "chevron-down" : "chevron-right",
29815
29848
  size: "xs",
29816
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
29849
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
29817
29850
  }
29818
- ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29851
+ ) }) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29819
29852
  /* @__PURE__ */ jsx(
29820
29853
  Icon,
29821
29854
  {
29822
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29855
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29823
29856
  size: "xs",
29824
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
29857
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
29825
29858
  }
29826
29859
  ),
29827
- /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
29860
+ /* @__PURE__ */ jsx(
29861
+ Typography,
29862
+ {
29863
+ variant: "caption",
29864
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
29865
+ children: item.label
29866
+ }
29867
+ ),
29868
+ onNodeAction && /* @__PURE__ */ jsx(
29869
+ Box,
29870
+ {
29871
+ onClick: handleAction,
29872
+ role: "button",
29873
+ "aria-label": nodeActionLabel ?? "Node action",
29874
+ title: nodeActionLabel,
29875
+ 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"}`,
29876
+ children: /* @__PURE__ */ jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
29877
+ }
29878
+ )
29828
29879
  ]
29829
29880
  }
29830
29881
  ),
@@ -29835,47 +29886,116 @@ var init_FileTree = __esm({
29835
29886
  depth: depth + 1,
29836
29887
  indent,
29837
29888
  childrenByParent,
29838
- onNodeSelect
29889
+ onNodeSelect,
29890
+ onNodeAction,
29891
+ nodeActionIcon,
29892
+ nodeActionLabel,
29893
+ selectedId,
29894
+ look,
29895
+ isExpanded,
29896
+ onToggle
29839
29897
  },
29840
29898
  child.id
29841
29899
  )) })
29842
29900
  ] });
29843
29901
  };
29902
+ FlatFileTree = ({
29903
+ items,
29904
+ selectedId,
29905
+ look = "files",
29906
+ onNodeSelect,
29907
+ onNodeAction,
29908
+ nodeActionIcon,
29909
+ nodeActionLabel,
29910
+ className,
29911
+ indent = 16
29912
+ }) => {
29913
+ const [overrides, setOverrides] = useState(() => /* @__PURE__ */ new Map());
29914
+ const byId = React94__default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
29915
+ const selectedAncestors = React94__default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
29916
+ const lastSelectedRef = React94__default.useRef(selectedId);
29917
+ React94__default.useEffect(() => {
29918
+ if (selectedId === lastSelectedRef.current) return;
29919
+ lastSelectedRef.current = selectedId;
29920
+ setOverrides((prev) => {
29921
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
29922
+ const next = new Map(prev);
29923
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
29924
+ return next;
29925
+ });
29926
+ }, [selectedId, selectedAncestors]);
29927
+ const isExpanded = useCallback(
29928
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
29929
+ [overrides, selectedAncestors]
29930
+ );
29931
+ const onToggle = useCallback((id, effective) => {
29932
+ setOverrides((prev) => {
29933
+ const next = new Map(prev);
29934
+ next.set(id, !effective);
29935
+ return next;
29936
+ });
29937
+ }, []);
29938
+ if (items.length === 0) return null;
29939
+ const ids = new Set(items.map((node) => node.id));
29940
+ const childrenByParent = /* @__PURE__ */ new Map();
29941
+ const roots = [];
29942
+ for (const item of items) {
29943
+ if (item.parentId && ids.has(item.parentId)) {
29944
+ const siblings = childrenByParent.get(item.parentId);
29945
+ if (siblings) siblings.push(item);
29946
+ else childrenByParent.set(item.parentId, [item]);
29947
+ } else {
29948
+ roots.push(item);
29949
+ }
29950
+ }
29951
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
29952
+ FlatTreeNodeItem,
29953
+ {
29954
+ item,
29955
+ depth: 0,
29956
+ indent,
29957
+ childrenByParent,
29958
+ onNodeSelect,
29959
+ onNodeAction,
29960
+ nodeActionIcon,
29961
+ nodeActionLabel,
29962
+ selectedId,
29963
+ look,
29964
+ isExpanded,
29965
+ onToggle
29966
+ },
29967
+ item.id
29968
+ )) });
29969
+ };
29844
29970
  FileTree = ({
29845
29971
  tree,
29846
29972
  items,
29847
29973
  selectedPath,
29974
+ selectedId,
29975
+ look = "files",
29848
29976
  onFileSelect,
29849
29977
  onNodeSelect,
29978
+ onNodeAction,
29979
+ nodeActionIcon,
29980
+ nodeActionLabel,
29850
29981
  className,
29851
29982
  indent = 16
29852
29983
  }) => {
29853
29984
  if (items) {
29854
- if (items.length === 0) return null;
29855
- const ids = new Set(items.map((node) => node.id));
29856
- const childrenByParent = /* @__PURE__ */ new Map();
29857
- const roots = [];
29858
- for (const item of items) {
29859
- if (item.parentId && ids.has(item.parentId)) {
29860
- const siblings = childrenByParent.get(item.parentId);
29861
- if (siblings) siblings.push(item);
29862
- else childrenByParent.set(item.parentId, [item]);
29863
- } else {
29864
- roots.push(item);
29865
- }
29866
- }
29867
- return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
29868
- FlatTreeNodeItem,
29985
+ return /* @__PURE__ */ jsx(
29986
+ FlatFileTree,
29869
29987
  {
29870
- item,
29871
- depth: 0,
29872
- indent,
29873
- childrenByParent,
29988
+ items,
29989
+ selectedId,
29990
+ look,
29874
29991
  onNodeSelect,
29875
- defaultExpanded: true
29876
- },
29877
- item.id
29878
- )) });
29992
+ onNodeAction,
29993
+ nodeActionIcon,
29994
+ nodeActionLabel,
29995
+ className,
29996
+ indent
29997
+ }
29998
+ );
29879
29999
  }
29880
30000
  if (!tree || tree.length === 0) return null;
29881
30001
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
@@ -35340,6 +35460,33 @@ var init_Lightbox = __esm({
35340
35460
  Lightbox.displayName = "Lightbox";
35341
35461
  }
35342
35462
  });
35463
+
35464
+ // lib/relationLabel.ts
35465
+ function relationLabel(value) {
35466
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
35467
+ return null;
35468
+ for (const key of ["name", "title", "label"]) {
35469
+ const candidate = value[key];
35470
+ if (typeof candidate === "string" && candidate !== "") return candidate;
35471
+ }
35472
+ const id = value.id;
35473
+ return id !== void 0 && id !== null ? String(id) : null;
35474
+ }
35475
+ function relationDisplayLabels(value, options) {
35476
+ if (value === void 0 || value === null || value === "") return [];
35477
+ if (Array.isArray(value)) {
35478
+ return value.flatMap((item) => relationDisplayLabels(item, options));
35479
+ }
35480
+ const hydrated = relationLabel(value);
35481
+ if (hydrated !== null) return [hydrated];
35482
+ const raw = String(value);
35483
+ const match = options?.find((opt) => opt.value === raw);
35484
+ return [match ? match.label : raw];
35485
+ }
35486
+ var init_relationLabel = __esm({
35487
+ "lib/relationLabel.ts"() {
35488
+ }
35489
+ });
35343
35490
  function renderIconInput3(icon, props) {
35344
35491
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
35345
35492
  }
@@ -35657,6 +35804,7 @@ var init_TableView = __esm({
35657
35804
  "use client";
35658
35805
  init_cn();
35659
35806
  init_format();
35807
+ init_relationLabel();
35660
35808
  init_getNestedValue();
35661
35809
  init_useEventBus();
35662
35810
  init_Box();
@@ -35670,7 +35818,13 @@ var init_TableView = __esm({
35670
35818
  init_Menu();
35671
35819
  init_useDataDnd();
35672
35820
  tableViewLog = createLogger("almadar:ui:table-view");
35673
- formatCell = (value, format) => formatValue(value, format);
35821
+ formatCell = (value, format) => {
35822
+ if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
35823
+ const labels = relationDisplayLabels(value);
35824
+ if (labels.length > 0) return labels.join(", ");
35825
+ }
35826
+ return formatValue(value, format);
35827
+ };
35674
35828
  MAX_MEASURED_COL_CH = 32;
35675
35829
  BADGE_CHROME_CH = 4;
35676
35830
  alignClass = {
@@ -39285,6 +39439,35 @@ var init_RichTextEditor = __esm({
39285
39439
  document.execCommand("createLink", false, safe);
39286
39440
  afterEdit();
39287
39441
  }, [afterEdit, t, toolbar.link]);
39442
+ const imageInputRef = useRef(null);
39443
+ const savedRangeRef = useRef(null);
39444
+ const execImage = useCallback(() => {
39445
+ const root = ref.current;
39446
+ const sel = window.getSelection();
39447
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
39448
+ imageInputRef.current?.click();
39449
+ }, []);
39450
+ const handleImageFile = useCallback((e) => {
39451
+ const file = e.target.files?.[0];
39452
+ e.target.value = "";
39453
+ if (!file) return;
39454
+ const reader = new FileReader();
39455
+ reader.onload = () => {
39456
+ const url = typeof reader.result === "string" ? reader.result : "";
39457
+ if (!url.startsWith("data:image/")) return;
39458
+ const root = ref.current;
39459
+ if (!root) return;
39460
+ root.focus();
39461
+ const sel = window.getSelection();
39462
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
39463
+ sel.removeAllRanges();
39464
+ sel.addRange(savedRangeRef.current);
39465
+ }
39466
+ document.execCommand("insertImage", false, url);
39467
+ afterEdit();
39468
+ };
39469
+ reader.readAsDataURL(file);
39470
+ }, [afterEdit]);
39288
39471
  const execRule = useCallback(() => {
39289
39472
  document.execCommand("insertHorizontalRule", false);
39290
39473
  afterEdit();
@@ -39297,6 +39480,18 @@ var init_RichTextEditor = __esm({
39297
39480
  }
39298
39481
  return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
39299
39482
  /* @__PURE__ */ jsx(RichTextStyles, {}),
39483
+ /* @__PURE__ */ jsx(
39484
+ "input",
39485
+ {
39486
+ ref: imageInputRef,
39487
+ type: "file",
39488
+ accept: "image/*",
39489
+ className: "hidden",
39490
+ "aria-hidden": "true",
39491
+ tabIndex: -1,
39492
+ onChange: handleImageFile
39493
+ }
39494
+ ),
39300
39495
  showToolbar && /* @__PURE__ */ jsxs(
39301
39496
  Box,
39302
39497
  {
@@ -39322,6 +39517,7 @@ var init_RichTextEditor = __esm({
39322
39517
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
39323
39518
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
39324
39519
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Link$1, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
39520
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Image$1, label: t("richTextEditor.image"), onExec: execImage }),
39325
39521
  /* @__PURE__ */ jsx(ToolbarButton, { icon: Minus, label: t("richTextEditor.divider"), onExec: execRule })
39326
39522
  ]
39327
39523
  }
@@ -39362,6 +39558,8 @@ function DocumentPanel({
39362
39558
  id,
39363
39559
  title,
39364
39560
  subtitle,
39561
+ coverImage,
39562
+ icon,
39365
39563
  value,
39366
39564
  actions,
39367
39565
  maxInlineActions,
@@ -39437,62 +39635,74 @@ function DocumentPanel({
39437
39635
  const emitWithId = (event) => () => {
39438
39636
  if (event) eventBus.emit(`UI:${event}`, { id: recordId });
39439
39637
  };
39440
- return /* @__PURE__ */ jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
39441
- /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
39442
- /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
39443
- titleNode,
39444
- subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
39445
- ] }),
39446
- /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
39447
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
39448
- /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
39449
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
39450
- editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
39451
- /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
39452
- editLabel ?? (t("documentPanel.edit") || "Edit")
39453
- ] }),
39454
- inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
39455
- Button,
39456
- {
39457
- variant: "primary",
39458
- size: "sm",
39459
- onClick: () => fireAction(action),
39460
- "data-testid": `action-${action.event}`,
39461
- children: [
39462
- action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
39463
- action.label
39464
- ]
39465
- },
39466
- idx
39467
- )),
39468
- menuActions.length > 0 && /* @__PURE__ */ jsx(
39469
- Menu,
39470
- {
39471
- position: "bottom-end",
39472
- 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" }) }),
39473
- items: menuActions.map((action) => ({
39474
- label: action.label,
39475
- icon: action.icon,
39476
- variant: action.variant === "danger" ? "danger" : "default",
39477
- onClick: () => fireAction(action)
39478
- }))
39479
- }
39480
- )
39481
- ] }) })
39482
- ] }),
39483
- editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
39484
- Box,
39638
+ return /* @__PURE__ */ jsxs(Card, { variant: "elevated", className: cn("w-full overflow-hidden", className), children: [
39639
+ coverImage ? /* @__PURE__ */ jsx(Box, { className: "h-44 w-full sm:h-52", "data-testid": "document-cover", children: /* @__PURE__ */ jsx(
39640
+ "img",
39485
39641
  {
39486
- onClick: emitWithId(editEvent),
39487
- className: cn(
39488
- "rounded-md px-1 -mx-1 min-h-[16rem]",
39489
- editEvent && "cursor-text transition-colors hover:bg-muted/30"
39490
- ),
39491
- "data-testid": "document-body",
39492
- children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
39493
- }
39494
- )
39495
- ] }) });
39642
+ src: coverImage,
39643
+ alt: "",
39644
+ "aria-hidden": "true",
39645
+ className: "h-full w-full object-cover"
39646
+ }
39647
+ ) }) : null,
39648
+ /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
39649
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
39650
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
39651
+ icon ? /* @__PURE__ */ jsx(Box, { className: "text-muted-foreground", "data-testid": "document-icon", children: renderIconInput4(icon, { size: "lg" }) }) : null,
39652
+ titleNode,
39653
+ subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
39654
+ ] }),
39655
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
39656
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
39657
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
39658
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
39659
+ editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
39660
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
39661
+ editLabel ?? (t("documentPanel.edit") || "Edit")
39662
+ ] }),
39663
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
39664
+ Button,
39665
+ {
39666
+ variant: "primary",
39667
+ size: "sm",
39668
+ onClick: () => fireAction(action),
39669
+ "data-testid": `action-${action.event}`,
39670
+ children: [
39671
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
39672
+ action.label
39673
+ ]
39674
+ },
39675
+ idx
39676
+ )),
39677
+ menuActions.length > 0 && /* @__PURE__ */ jsx(
39678
+ Menu,
39679
+ {
39680
+ position: "bottom-end",
39681
+ 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" }) }),
39682
+ items: menuActions.map((action) => ({
39683
+ label: action.label,
39684
+ icon: action.icon,
39685
+ variant: action.variant === "danger" ? "danger" : "default",
39686
+ onClick: () => fireAction(action)
39687
+ }))
39688
+ }
39689
+ )
39690
+ ] }) })
39691
+ ] }),
39692
+ editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
39693
+ Box,
39694
+ {
39695
+ onClick: emitWithId(editEvent),
39696
+ className: cn(
39697
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
39698
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
39699
+ ),
39700
+ "data-testid": "document-body",
39701
+ children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
39702
+ }
39703
+ )
39704
+ ] })
39705
+ ] });
39496
39706
  }
39497
39707
  var init_DocumentPanel = __esm({
39498
39708
  "components/core/molecules/DocumentPanel.tsx"() {
@@ -39516,17 +39726,16 @@ function renderIconInput5(icon, props) {
39516
39726
  function fieldName(field) {
39517
39727
  return field.name ?? field.key ?? "";
39518
39728
  }
39519
- function relationLabel(value) {
39520
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
39521
- for (const key of ["name", "title", "label"]) {
39522
- const candidate = value[key];
39523
- if (typeof candidate === "string" && candidate !== "") return candidate;
39729
+ function relationId(value) {
39730
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
39731
+ const id = value.id;
39732
+ if (id !== void 0 && id !== null) return String(id);
39524
39733
  }
39525
- const id = value.id;
39526
- return id !== void 0 && id !== null ? String(id) : null;
39734
+ return String(value);
39527
39735
  }
39528
39736
  function fieldKind(field, value) {
39529
39737
  if (field.kind) return field.kind;
39738
+ if (field.type === "image") return "image";
39530
39739
  if (field.options && field.options.length > 0) return "select";
39531
39740
  if (typeof value === "boolean" || field.format === "boolean") return "boolean";
39532
39741
  if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
@@ -39536,6 +39745,7 @@ function DocumentDetails({
39536
39745
  entity,
39537
39746
  fields,
39538
39747
  metaCommitEvent,
39748
+ relationEvent,
39539
39749
  title,
39540
39750
  className
39541
39751
  }) {
@@ -39550,11 +39760,51 @@ function DocumentDetails({
39550
39760
  if (!metaCommitEvent || next === current) return;
39551
39761
  eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
39552
39762
  };
39763
+ const relationChip = (item, name, key) => {
39764
+ const label = relationLabel(item) ?? humanizeEnumValue(String(item));
39765
+ if (!relationEvent) {
39766
+ return /* @__PURE__ */ jsx(Badge, { variant: "default", children: label }, key);
39767
+ }
39768
+ const emitClick = () => eventBus.emit(`UI:${relationEvent}`, { field: name, id: relationId(item), name: label });
39769
+ return /* @__PURE__ */ jsx(
39770
+ Badge,
39771
+ {
39772
+ variant: "default",
39773
+ role: "button",
39774
+ tabIndex: 0,
39775
+ className: "cursor-pointer transition-colors hover:bg-accent",
39776
+ onClick: emitClick,
39777
+ onKeyDown: (e) => {
39778
+ if (e.key === "Enter" || e.key === " ") {
39779
+ e.preventDefault();
39780
+ emitClick();
39781
+ }
39782
+ },
39783
+ "data-testid": `relation-chip-${name}`,
39784
+ children: label
39785
+ },
39786
+ key
39787
+ );
39788
+ };
39553
39789
  const renderValue = (field) => {
39554
39790
  const name = fieldName(field);
39555
39791
  const raw = getNestedValue(entity ?? {}, name);
39556
39792
  const kind = fieldKind(field, raw);
39557
39793
  const label = field.label ?? field.header ?? humanizeFieldName(name);
39794
+ if (kind === "image") {
39795
+ const url = typeof raw === "string" ? raw : "";
39796
+ if (!url) return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
39797
+ return /* @__PURE__ */ jsx(
39798
+ "img",
39799
+ {
39800
+ src: url,
39801
+ alt: label,
39802
+ loading: "lazy",
39803
+ className: "max-h-32 w-full rounded-md border border-border object-cover",
39804
+ "data-testid": `document-property-image-${name}`
39805
+ }
39806
+ );
39807
+ }
39558
39808
  if (kind === "boolean") {
39559
39809
  return /* @__PURE__ */ jsx(
39560
39810
  Switch,
@@ -39584,9 +39834,12 @@ function DocumentDetails({
39584
39834
  if (raw.length === 0) {
39585
39835
  return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
39586
39836
  }
39587
- 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)) });
39837
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => relationChip(item, name, i)) });
39588
39838
  }
39589
- const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
39839
+ if (raw !== void 0 && raw !== null && raw !== "" && relationLabel(raw) !== null) {
39840
+ return relationChip(raw, name);
39841
+ }
39842
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : formatValue(raw, field.format);
39590
39843
  return /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
39591
39844
  }
39592
39845
  if (fieldDraft?.name === name) {
@@ -39655,6 +39908,7 @@ var init_DocumentDetails = __esm({
39655
39908
  init_cn();
39656
39909
  init_format();
39657
39910
  init_getNestedValue();
39911
+ init_relationLabel();
39658
39912
  init_useEventBus();
39659
39913
  init_Box();
39660
39914
  init_Stack();
@@ -44240,7 +44494,18 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44240
44494
  if (value === void 0 || value === null) return "\u2014";
44241
44495
  const str = String(value);
44242
44496
  switch (fieldType) {
44243
- case "image":
44497
+ case "image": {
44498
+ if (!str) return "\u2014";
44499
+ return /* @__PURE__ */ jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsx(
44500
+ "img",
44501
+ {
44502
+ src: str,
44503
+ alt: formatFieldLabel(fieldName2),
44504
+ className: "max-w-full max-h-64 rounded-md object-contain",
44505
+ loading: "lazy"
44506
+ }
44507
+ ) });
44508
+ }
44244
44509
  case "url": {
44245
44510
  if (str.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str.startsWith("data:image/")) {
44246
44511
  return /* @__PURE__ */ jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsx(
@@ -44253,7 +44518,7 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44253
44518
  }
44254
44519
  ) });
44255
44520
  }
44256
- if (fieldType === "url" && /^https?:\/\//i.test(str)) {
44521
+ if (/^https?:\/\//i.test(str)) {
44257
44522
  return /* @__PURE__ */ jsx(
44258
44523
  "a",
44259
44524
  {
@@ -44339,8 +44604,10 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44339
44604
  return str;
44340
44605
  }
44341
44606
  case "relation": {
44342
- const match = meta?.options?.find((opt) => opt.value === str);
44343
- return match ? match.label : str;
44607
+ const labels = relationDisplayLabels(value, meta?.options);
44608
+ if (labels.length === 0) return "\u2014";
44609
+ if (labels.length === 1) return labels[0];
44610
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", wrap: true, children: labels.map((label, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: label }, i)) });
44344
44611
  }
44345
44612
  case "file": {
44346
44613
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -44442,6 +44709,7 @@ var init_DetailPanel = __esm({
44442
44709
  init_cn();
44443
44710
  init_format();
44444
44711
  init_getNestedValue();
44712
+ init_relationLabel();
44445
44713
  init_useEventBus();
44446
44714
  init_UploadDropZone();
44447
44715
  formatFieldLabel = humanizeFieldName;
@@ -45100,6 +45368,8 @@ function determineInputType(field) {
45100
45368
  return "url";
45101
45369
  case "file":
45102
45370
  return "file";
45371
+ case "image":
45372
+ return "image";
45103
45373
  case "money":
45104
45374
  return "currency";
45105
45375
  case "number":
@@ -45699,6 +45969,50 @@ var init_Form = __esm({
45699
45969
  }
45700
45970
  }
45701
45971
  );
45972
+ case "image": {
45973
+ const imageUrl = typeof currentValue === "string" ? currentValue : "";
45974
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
45975
+ imageUrl ? /* @__PURE__ */ jsx(
45976
+ "img",
45977
+ {
45978
+ src: imageUrl,
45979
+ alt: "",
45980
+ "aria-hidden": "true",
45981
+ className: "h-24 w-full max-w-xs rounded-md border border-border object-cover",
45982
+ "data-testid": `image-preview-${fieldName2}`
45983
+ }
45984
+ ) : null,
45985
+ /* @__PURE__ */ jsx(
45986
+ UploadDropZone,
45987
+ {
45988
+ accept: "image/*",
45989
+ maxFiles: 1,
45990
+ maxSize: 2 * 1024 * 1024,
45991
+ disabled: isLoading,
45992
+ onFiles: (files) => {
45993
+ const f3 = files[0];
45994
+ if (!f3) return;
45995
+ const reader = new FileReader();
45996
+ reader.onload = () => handleChange(
45997
+ fieldName2,
45998
+ typeof reader.result === "string" ? reader.result : ""
45999
+ );
46000
+ reader.readAsDataURL(f3);
46001
+ }
46002
+ }
46003
+ ),
46004
+ /* @__PURE__ */ jsx(
46005
+ Input,
46006
+ {
46007
+ ...commonProps,
46008
+ type: "url",
46009
+ placeholder: t("form.imageUrlFallback"),
46010
+ value: imageUrl,
46011
+ onChange: (e) => handleChange(fieldName2, e.target.value)
46012
+ }
46013
+ )
46014
+ ] });
46015
+ }
45702
46016
  case "date":
45703
46017
  return /* @__PURE__ */ jsx(
45704
46018
  Input,