@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.
@@ -22637,6 +22637,7 @@ var init_DashboardLayout = __esm({
22637
22637
  onNotificationClick,
22638
22638
  showThemeToggle = true,
22639
22639
  sidebarFooter,
22640
+ sidebarContent,
22640
22641
  onSignOut: onSignOutProp,
22641
22642
  currentPath,
22642
22643
  layoutMode = "sidebar",
@@ -22766,7 +22767,11 @@ var init_DashboardLayout = __esm({
22766
22767
  {
22767
22768
  as: "nav",
22768
22769
  gap: "none",
22769
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
22770
+ className: cn(
22771
+ "py-4 space-y-1 overflow-y-auto",
22772
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
22773
+ isRail ? "px-2" : "px-3"
22774
+ ),
22770
22775
  children: navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
22771
22776
  NavLink,
22772
22777
  {
@@ -22778,6 +22783,7 @@ var init_DashboardLayout = __esm({
22778
22783
  ))
22779
22784
  }
22780
22785
  ),
22786
+ sidebarContent && !isRail && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
22781
22787
  sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
22782
22788
  ]
22783
22789
  }
@@ -25848,7 +25854,17 @@ function fileIcon(name) {
25848
25854
  return "file";
25849
25855
  }
25850
25856
  }
25851
- var TreeNodeItem, FlatTreeNodeItem; exports.FileTree = void 0;
25857
+ function ancestorsOf(id, byId) {
25858
+ const out = /* @__PURE__ */ new Set();
25859
+ if (!id) return out;
25860
+ let cur = byId.get(id)?.parentId;
25861
+ while (cur && byId.has(cur) && !out.has(cur)) {
25862
+ out.add(cur);
25863
+ cur = byId.get(cur)?.parentId;
25864
+ }
25865
+ return out;
25866
+ }
25867
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree; exports.FileTree = void 0;
25852
25868
  var init_FileTree = __esm({
25853
25869
  "components/core/molecules/FileTree.tsx"() {
25854
25870
  "use client";
@@ -25939,42 +25955,77 @@ var init_FileTree = __esm({
25939
25955
  indent,
25940
25956
  childrenByParent,
25941
25957
  onNodeSelect,
25942
- defaultExpanded = false
25958
+ onNodeAction,
25959
+ nodeActionIcon,
25960
+ nodeActionLabel,
25961
+ selectedId,
25962
+ look,
25963
+ isExpanded,
25964
+ onToggle
25943
25965
  }) => {
25944
- const [expanded, setExpanded] = React77.useState(defaultExpanded || depth < 1);
25945
25966
  const children = childrenByParent.get(item.id);
25946
25967
  const hasChildren = !!children && children.length > 0;
25968
+ const expanded = hasChildren && isExpanded(item.id, depth);
25969
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
25970
+ const nav = look === "nav";
25947
25971
  const handleClick = React77.useCallback(() => {
25948
- if (hasChildren) setExpanded((prev) => !prev);
25972
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
25949
25973
  onNodeSelect?.(item.id);
25950
- }, [hasChildren, item.id, onNodeSelect]);
25974
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
25975
+ const handleChevron = React77.useCallback((e) => {
25976
+ e.stopPropagation();
25977
+ onToggle(item.id, expanded);
25978
+ }, [item.id, expanded, onToggle]);
25979
+ const handleAction = React77.useCallback((e) => {
25980
+ e.stopPropagation();
25981
+ onNodeAction?.(item.id);
25982
+ }, [item.id, onNodeAction]);
25951
25983
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
25952
25984
  /* @__PURE__ */ jsxRuntime.jsxs(
25953
25985
  exports.Box,
25954
25986
  {
25955
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
25987
+ 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"}`,
25956
25988
  style: { paddingLeft: depth * indent + 8 },
25957
25989
  onClick: handleClick,
25958
25990
  role: "treeitem",
25991
+ "aria-selected": isSelected,
25959
25992
  "aria-expanded": hasChildren ? expanded : void 0,
25960
25993
  children: [
25961
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
25994
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
25962
25995
  exports.Icon,
25963
25996
  {
25964
25997
  name: expanded ? "chevron-down" : "chevron-right",
25965
25998
  size: "xs",
25966
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
25999
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
25967
26000
  }
25968
- ) : /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { style: { width: 12, flexShrink: 0 } }),
26001
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { style: { width: 12, flexShrink: 0 } }),
25969
26002
  /* @__PURE__ */ jsxRuntime.jsx(
25970
26003
  exports.Icon,
25971
26004
  {
25972
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
26005
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25973
26006
  size: "xs",
25974
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
26007
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
25975
26008
  }
25976
26009
  ),
25977
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
26010
+ /* @__PURE__ */ jsxRuntime.jsx(
26011
+ exports.Typography,
26012
+ {
26013
+ variant: "caption",
26014
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
26015
+ children: item.label
26016
+ }
26017
+ ),
26018
+ onNodeAction && /* @__PURE__ */ jsxRuntime.jsx(
26019
+ exports.Box,
26020
+ {
26021
+ onClick: handleAction,
26022
+ role: "button",
26023
+ "aria-label": nodeActionLabel ?? "Node action",
26024
+ title: nodeActionLabel,
26025
+ 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"}`,
26026
+ children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
26027
+ }
26028
+ )
25978
26029
  ]
25979
26030
  }
25980
26031
  ),
@@ -25985,47 +26036,116 @@ var init_FileTree = __esm({
25985
26036
  depth: depth + 1,
25986
26037
  indent,
25987
26038
  childrenByParent,
25988
- onNodeSelect
26039
+ onNodeSelect,
26040
+ onNodeAction,
26041
+ nodeActionIcon,
26042
+ nodeActionLabel,
26043
+ selectedId,
26044
+ look,
26045
+ isExpanded,
26046
+ onToggle
25989
26047
  },
25990
26048
  child.id
25991
26049
  )) })
25992
26050
  ] });
25993
26051
  };
26052
+ FlatFileTree = ({
26053
+ items,
26054
+ selectedId,
26055
+ look = "files",
26056
+ onNodeSelect,
26057
+ onNodeAction,
26058
+ nodeActionIcon,
26059
+ nodeActionLabel,
26060
+ className,
26061
+ indent = 16
26062
+ }) => {
26063
+ const [overrides, setOverrides] = React77.useState(() => /* @__PURE__ */ new Map());
26064
+ const byId = React77__namespace.default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
26065
+ const selectedAncestors = React77__namespace.default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
26066
+ const lastSelectedRef = React77__namespace.default.useRef(selectedId);
26067
+ React77__namespace.default.useEffect(() => {
26068
+ if (selectedId === lastSelectedRef.current) return;
26069
+ lastSelectedRef.current = selectedId;
26070
+ setOverrides((prev) => {
26071
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
26072
+ const next = new Map(prev);
26073
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
26074
+ return next;
26075
+ });
26076
+ }, [selectedId, selectedAncestors]);
26077
+ const isExpanded = React77.useCallback(
26078
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
26079
+ [overrides, selectedAncestors]
26080
+ );
26081
+ const onToggle = React77.useCallback((id, effective) => {
26082
+ setOverrides((prev) => {
26083
+ const next = new Map(prev);
26084
+ next.set(id, !effective);
26085
+ return next;
26086
+ });
26087
+ }, []);
26088
+ if (items.length === 0) return null;
26089
+ const ids = new Set(items.map((node) => node.id));
26090
+ const childrenByParent = /* @__PURE__ */ new Map();
26091
+ const roots = [];
26092
+ for (const item of items) {
26093
+ if (item.parentId && ids.has(item.parentId)) {
26094
+ const siblings = childrenByParent.get(item.parentId);
26095
+ if (siblings) siblings.push(item);
26096
+ else childrenByParent.set(item.parentId, [item]);
26097
+ } else {
26098
+ roots.push(item);
26099
+ }
26100
+ }
26101
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
26102
+ FlatTreeNodeItem,
26103
+ {
26104
+ item,
26105
+ depth: 0,
26106
+ indent,
26107
+ childrenByParent,
26108
+ onNodeSelect,
26109
+ onNodeAction,
26110
+ nodeActionIcon,
26111
+ nodeActionLabel,
26112
+ selectedId,
26113
+ look,
26114
+ isExpanded,
26115
+ onToggle
26116
+ },
26117
+ item.id
26118
+ )) });
26119
+ };
25994
26120
  exports.FileTree = ({
25995
26121
  tree,
25996
26122
  items,
25997
26123
  selectedPath,
26124
+ selectedId,
26125
+ look = "files",
25998
26126
  onFileSelect,
25999
26127
  onNodeSelect,
26128
+ onNodeAction,
26129
+ nodeActionIcon,
26130
+ nodeActionLabel,
26000
26131
  className,
26001
26132
  indent = 16
26002
26133
  }) => {
26003
26134
  if (items) {
26004
- if (items.length === 0) return null;
26005
- const ids = new Set(items.map((node) => node.id));
26006
- const childrenByParent = /* @__PURE__ */ new Map();
26007
- const roots = [];
26008
- for (const item of items) {
26009
- if (item.parentId && ids.has(item.parentId)) {
26010
- const siblings = childrenByParent.get(item.parentId);
26011
- if (siblings) siblings.push(item);
26012
- else childrenByParent.set(item.parentId, [item]);
26013
- } else {
26014
- roots.push(item);
26015
- }
26016
- }
26017
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
26018
- FlatTreeNodeItem,
26135
+ return /* @__PURE__ */ jsxRuntime.jsx(
26136
+ FlatFileTree,
26019
26137
  {
26020
- item,
26021
- depth: 0,
26022
- indent,
26023
- childrenByParent,
26138
+ items,
26139
+ selectedId,
26140
+ look,
26024
26141
  onNodeSelect,
26025
- defaultExpanded: true
26026
- },
26027
- item.id
26028
- )) });
26142
+ onNodeAction,
26143
+ nodeActionIcon,
26144
+ nodeActionLabel,
26145
+ className,
26146
+ indent
26147
+ }
26148
+ );
26029
26149
  }
26030
26150
  if (!tree || tree.length === 0) return null;
26031
26151
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -34180,6 +34300,33 @@ var init_Lightbox = __esm({
34180
34300
  exports.Lightbox.displayName = "Lightbox";
34181
34301
  }
34182
34302
  });
34303
+
34304
+ // lib/relationLabel.ts
34305
+ function relationLabel(value) {
34306
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
34307
+ return null;
34308
+ for (const key of ["name", "title", "label"]) {
34309
+ const candidate = value[key];
34310
+ if (typeof candidate === "string" && candidate !== "") return candidate;
34311
+ }
34312
+ const id = value.id;
34313
+ return id !== void 0 && id !== null ? String(id) : null;
34314
+ }
34315
+ function relationDisplayLabels(value, options) {
34316
+ if (value === void 0 || value === null || value === "") return [];
34317
+ if (Array.isArray(value)) {
34318
+ return value.flatMap((item) => relationDisplayLabels(item, options));
34319
+ }
34320
+ const hydrated = relationLabel(value);
34321
+ if (hydrated !== null) return [hydrated];
34322
+ const raw = String(value);
34323
+ const match = options?.find((opt) => opt.value === raw);
34324
+ return [match ? match.label : raw];
34325
+ }
34326
+ var init_relationLabel = __esm({
34327
+ "lib/relationLabel.ts"() {
34328
+ }
34329
+ });
34183
34330
  function renderIconInput3(icon, props) {
34184
34331
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
34185
34332
  }
@@ -34497,6 +34644,7 @@ var init_TableView = __esm({
34497
34644
  "use client";
34498
34645
  init_cn();
34499
34646
  init_format();
34647
+ init_relationLabel();
34500
34648
  init_getNestedValue();
34501
34649
  init_useEventBus();
34502
34650
  init_Box();
@@ -34510,7 +34658,13 @@ var init_TableView = __esm({
34510
34658
  init_Menu();
34511
34659
  init_useDataDnd();
34512
34660
  tableViewLog = logger.createLogger("almadar:ui:table-view");
34513
- formatCell = (value, format) => formatValue(value, format);
34661
+ formatCell = (value, format) => {
34662
+ if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
34663
+ const labels = relationDisplayLabels(value);
34664
+ if (labels.length > 0) return labels.join(", ");
34665
+ }
34666
+ return formatValue(value, format);
34667
+ };
34514
34668
  MAX_MEASURED_COL_CH = 32;
34515
34669
  BADGE_CHROME_CH = 4;
34516
34670
  alignClass = {
@@ -38125,6 +38279,35 @@ var init_RichTextEditor = __esm({
38125
38279
  document.execCommand("createLink", false, safe);
38126
38280
  afterEdit();
38127
38281
  }, [afterEdit, t, toolbar.link]);
38282
+ const imageInputRef = React77.useRef(null);
38283
+ const savedRangeRef = React77.useRef(null);
38284
+ const execImage = React77.useCallback(() => {
38285
+ const root = ref.current;
38286
+ const sel = window.getSelection();
38287
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
38288
+ imageInputRef.current?.click();
38289
+ }, []);
38290
+ const handleImageFile = React77.useCallback((e) => {
38291
+ const file = e.target.files?.[0];
38292
+ e.target.value = "";
38293
+ if (!file) return;
38294
+ const reader = new FileReader();
38295
+ reader.onload = () => {
38296
+ const url = typeof reader.result === "string" ? reader.result : "";
38297
+ if (!url.startsWith("data:image/")) return;
38298
+ const root = ref.current;
38299
+ if (!root) return;
38300
+ root.focus();
38301
+ const sel = window.getSelection();
38302
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
38303
+ sel.removeAllRanges();
38304
+ sel.addRange(savedRangeRef.current);
38305
+ }
38306
+ document.execCommand("insertImage", false, url);
38307
+ afterEdit();
38308
+ };
38309
+ reader.readAsDataURL(file);
38310
+ }, [afterEdit]);
38128
38311
  const execRule = React77.useCallback(() => {
38129
38312
  document.execCommand("insertHorizontalRule", false);
38130
38313
  afterEdit();
@@ -38137,6 +38320,18 @@ var init_RichTextEditor = __esm({
38137
38320
  }
38138
38321
  return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: cn("flex flex-col gap-2", className), children: [
38139
38322
  /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
38323
+ /* @__PURE__ */ jsxRuntime.jsx(
38324
+ "input",
38325
+ {
38326
+ ref: imageInputRef,
38327
+ type: "file",
38328
+ accept: "image/*",
38329
+ className: "hidden",
38330
+ "aria-hidden": "true",
38331
+ tabIndex: -1,
38332
+ onChange: handleImageFile
38333
+ }
38334
+ ),
38140
38335
  showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
38141
38336
  exports.Box,
38142
38337
  {
@@ -38162,6 +38357,7 @@ var init_RichTextEditor = __esm({
38162
38357
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
38163
38358
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
38164
38359
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Link, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
38360
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Image, label: t("richTextEditor.image"), onExec: execImage }),
38165
38361
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Minus, label: t("richTextEditor.divider"), onExec: execRule })
38166
38362
  ]
38167
38363
  }
@@ -38202,6 +38398,8 @@ function DocumentPanel({
38202
38398
  id,
38203
38399
  title,
38204
38400
  subtitle,
38401
+ coverImage,
38402
+ icon,
38205
38403
  value,
38206
38404
  actions,
38207
38405
  maxInlineActions,
@@ -38277,62 +38475,74 @@ function DocumentPanel({
38277
38475
  const emitWithId = (event) => () => {
38278
38476
  if (event) eventBus.emit(`UI:${event}`, { id: recordId });
38279
38477
  };
38280
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38281
- /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "sm", className: "justify-between items-start", children: [
38282
- /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38283
- titleNode,
38284
- subtitle ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38285
- ] }),
38286
- /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38287
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38288
- /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38289
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38290
- editEvent && /* @__PURE__ */ jsxRuntime.jsxs(exports.Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38291
- /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "edit", size: "xs", className: "mr-1" }),
38292
- editLabel ?? (t("documentPanel.edit") || "Edit")
38293
- ] }),
38294
- inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
38295
- exports.Button,
38296
- {
38297
- variant: "primary",
38298
- size: "sm",
38299
- onClick: () => fireAction(action),
38300
- "data-testid": `action-${action.event}`,
38301
- children: [
38302
- action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38303
- action.label
38304
- ]
38305
- },
38306
- idx
38307
- )),
38308
- menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
38309
- exports.Menu,
38310
- {
38311
- position: "bottom-end",
38312
- trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
38313
- items: menuActions.map((action) => ({
38314
- label: action.label,
38315
- icon: action.icon,
38316
- variant: action.variant === "danger" ? "danger" : "default",
38317
- onClick: () => fireAction(action)
38318
- }))
38319
- }
38320
- )
38321
- ] }) })
38322
- ] }),
38323
- editing ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
38324
- exports.Box,
38478
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.Card, { variant: "elevated", className: cn("w-full overflow-hidden", className), children: [
38479
+ coverImage ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "h-44 w-full sm:h-52", "data-testid": "document-cover", children: /* @__PURE__ */ jsxRuntime.jsx(
38480
+ "img",
38325
38481
  {
38326
- onClick: emitWithId(editEvent),
38327
- className: cn(
38328
- "rounded-md px-1 -mx-1 min-h-[16rem]",
38329
- editEvent && "cursor-text transition-colors hover:bg-muted/30"
38330
- ),
38331
- "data-testid": "document-body",
38332
- children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
38333
- }
38334
- )
38335
- ] }) });
38482
+ src: coverImage,
38483
+ alt: "",
38484
+ "aria-hidden": "true",
38485
+ className: "h-full w-full object-cover"
38486
+ }
38487
+ ) }) : null,
38488
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38489
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "sm", className: "justify-between items-start", children: [
38490
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38491
+ icon ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-muted-foreground", "data-testid": "document-icon", children: renderIconInput4(icon, { size: "lg" }) }) : null,
38492
+ titleNode,
38493
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38494
+ ] }),
38495
+ /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38496
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38497
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38498
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38499
+ editEvent && /* @__PURE__ */ jsxRuntime.jsxs(exports.Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38500
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "edit", size: "xs", className: "mr-1" }),
38501
+ editLabel ?? (t("documentPanel.edit") || "Edit")
38502
+ ] }),
38503
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
38504
+ exports.Button,
38505
+ {
38506
+ variant: "primary",
38507
+ size: "sm",
38508
+ onClick: () => fireAction(action),
38509
+ "data-testid": `action-${action.event}`,
38510
+ children: [
38511
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38512
+ action.label
38513
+ ]
38514
+ },
38515
+ idx
38516
+ )),
38517
+ menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
38518
+ exports.Menu,
38519
+ {
38520
+ position: "bottom-end",
38521
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
38522
+ items: menuActions.map((action) => ({
38523
+ label: action.label,
38524
+ icon: action.icon,
38525
+ variant: action.variant === "danger" ? "danger" : "default",
38526
+ onClick: () => fireAction(action)
38527
+ }))
38528
+ }
38529
+ )
38530
+ ] }) })
38531
+ ] }),
38532
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
38533
+ exports.Box,
38534
+ {
38535
+ onClick: emitWithId(editEvent),
38536
+ className: cn(
38537
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
38538
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
38539
+ ),
38540
+ "data-testid": "document-body",
38541
+ children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
38542
+ }
38543
+ )
38544
+ ] })
38545
+ ] });
38336
38546
  }
38337
38547
  var init_DocumentPanel = __esm({
38338
38548
  "components/core/molecules/DocumentPanel.tsx"() {
@@ -38356,17 +38566,16 @@ function renderIconInput5(icon, props) {
38356
38566
  function fieldName(field) {
38357
38567
  return field.name ?? field.key ?? "";
38358
38568
  }
38359
- function relationLabel(value) {
38360
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
38361
- for (const key of ["name", "title", "label"]) {
38362
- const candidate = value[key];
38363
- if (typeof candidate === "string" && candidate !== "") return candidate;
38569
+ function relationId(value) {
38570
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
38571
+ const id = value.id;
38572
+ if (id !== void 0 && id !== null) return String(id);
38364
38573
  }
38365
- const id = value.id;
38366
- return id !== void 0 && id !== null ? String(id) : null;
38574
+ return String(value);
38367
38575
  }
38368
38576
  function fieldKind(field, value) {
38369
38577
  if (field.kind) return field.kind;
38578
+ if (field.type === "image") return "image";
38370
38579
  if (field.options && field.options.length > 0) return "select";
38371
38580
  if (typeof value === "boolean" || field.format === "boolean") return "boolean";
38372
38581
  if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
@@ -38376,6 +38585,7 @@ function DocumentDetails({
38376
38585
  entity,
38377
38586
  fields,
38378
38587
  metaCommitEvent,
38588
+ relationEvent,
38379
38589
  title,
38380
38590
  className
38381
38591
  }) {
@@ -38390,11 +38600,51 @@ function DocumentDetails({
38390
38600
  if (!metaCommitEvent || next === current) return;
38391
38601
  eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
38392
38602
  };
38603
+ const relationChip = (item, name, key) => {
38604
+ const label = relationLabel(item) ?? humanizeEnumValue(String(item));
38605
+ if (!relationEvent) {
38606
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", children: label }, key);
38607
+ }
38608
+ const emitClick = () => eventBus.emit(`UI:${relationEvent}`, { field: name, id: relationId(item), name: label });
38609
+ return /* @__PURE__ */ jsxRuntime.jsx(
38610
+ exports.Badge,
38611
+ {
38612
+ variant: "default",
38613
+ role: "button",
38614
+ tabIndex: 0,
38615
+ className: "cursor-pointer transition-colors hover:bg-accent",
38616
+ onClick: emitClick,
38617
+ onKeyDown: (e) => {
38618
+ if (e.key === "Enter" || e.key === " ") {
38619
+ e.preventDefault();
38620
+ emitClick();
38621
+ }
38622
+ },
38623
+ "data-testid": `relation-chip-${name}`,
38624
+ children: label
38625
+ },
38626
+ key
38627
+ );
38628
+ };
38393
38629
  const renderValue = (field) => {
38394
38630
  const name = fieldName(field);
38395
38631
  const raw = getNestedValue(entity ?? {}, name);
38396
38632
  const kind = fieldKind(field, raw);
38397
38633
  const label = field.label ?? field.header ?? humanizeFieldName(name);
38634
+ if (kind === "image") {
38635
+ const url = typeof raw === "string" ? raw : "";
38636
+ if (!url) return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: "\u2014" });
38637
+ return /* @__PURE__ */ jsxRuntime.jsx(
38638
+ "img",
38639
+ {
38640
+ src: url,
38641
+ alt: label,
38642
+ loading: "lazy",
38643
+ className: "max-h-32 w-full rounded-md border border-border object-cover",
38644
+ "data-testid": `document-property-image-${name}`
38645
+ }
38646
+ );
38647
+ }
38398
38648
  if (kind === "boolean") {
38399
38649
  return /* @__PURE__ */ jsxRuntime.jsx(
38400
38650
  exports.Switch,
@@ -38424,9 +38674,12 @@ function DocumentDetails({
38424
38674
  if (raw.length === 0) {
38425
38675
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: "\u2014" });
38426
38676
  }
38427
- return /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
38677
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => relationChip(item, name, i)) });
38428
38678
  }
38429
- const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
38679
+ if (raw !== void 0 && raw !== null && raw !== "" && relationLabel(raw) !== null) {
38680
+ return relationChip(raw, name);
38681
+ }
38682
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : formatValue(raw, field.format);
38430
38683
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", className: "break-words", children: shown2 });
38431
38684
  }
38432
38685
  if (fieldDraft?.name === name) {
@@ -38495,6 +38748,7 @@ var init_DocumentDetails = __esm({
38495
38748
  init_cn();
38496
38749
  init_format();
38497
38750
  init_getNestedValue();
38751
+ init_relationLabel();
38498
38752
  init_useEventBus();
38499
38753
  init_Box();
38500
38754
  init_Stack();
@@ -43716,7 +43970,18 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43716
43970
  if (value === void 0 || value === null) return "\u2014";
43717
43971
  const str2 = String(value);
43718
43972
  switch (fieldType) {
43719
- case "image":
43973
+ case "image": {
43974
+ if (!str2) return "\u2014";
43975
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
43976
+ "img",
43977
+ {
43978
+ src: str2,
43979
+ alt: formatFieldLabel(fieldName2),
43980
+ className: "max-w-full max-h-64 rounded-md object-contain",
43981
+ loading: "lazy"
43982
+ }
43983
+ ) });
43984
+ }
43720
43985
  case "url": {
43721
43986
  if (str2.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str2.startsWith("data:image/")) {
43722
43987
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -43729,7 +43994,7 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43729
43994
  }
43730
43995
  ) });
43731
43996
  }
43732
- if (fieldType === "url" && /^https?:\/\//i.test(str2)) {
43997
+ if (/^https?:\/\//i.test(str2)) {
43733
43998
  return /* @__PURE__ */ jsxRuntime.jsx(
43734
43999
  "a",
43735
44000
  {
@@ -43815,8 +44080,10 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43815
44080
  return str2;
43816
44081
  }
43817
44082
  case "relation": {
43818
- const match = meta?.options?.find((opt) => opt.value === str2);
43819
- return match ? match.label : str2;
44083
+ const labels = relationDisplayLabels(value, meta?.options);
44084
+ if (labels.length === 0) return "\u2014";
44085
+ if (labels.length === 1) return labels[0];
44086
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", wrap: true, children: labels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", children: label }, i)) });
43820
44087
  }
43821
44088
  case "file": {
43822
44089
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -43918,6 +44185,7 @@ var init_DetailPanel = __esm({
43918
44185
  init_cn();
43919
44186
  init_format();
43920
44187
  init_getNestedValue();
44188
+ init_relationLabel();
43921
44189
  init_useEventBus();
43922
44190
  init_UploadDropZone();
43923
44191
  formatFieldLabel = humanizeFieldName;
@@ -44576,6 +44844,8 @@ function determineInputType(field) {
44576
44844
  return "url";
44577
44845
  case "file":
44578
44846
  return "file";
44847
+ case "image":
44848
+ return "image";
44579
44849
  case "money":
44580
44850
  return "currency";
44581
44851
  case "number":
@@ -45175,6 +45445,50 @@ var init_Form = __esm({
45175
45445
  }
45176
45446
  }
45177
45447
  );
45448
+ case "image": {
45449
+ const imageUrl = typeof currentValue2 === "string" ? currentValue2 : "";
45450
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
45451
+ imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
45452
+ "img",
45453
+ {
45454
+ src: imageUrl,
45455
+ alt: "",
45456
+ "aria-hidden": "true",
45457
+ className: "h-24 w-full max-w-xs rounded-md border border-border object-cover",
45458
+ "data-testid": `image-preview-${fieldName2}`
45459
+ }
45460
+ ) : null,
45461
+ /* @__PURE__ */ jsxRuntime.jsx(
45462
+ exports.UploadDropZone,
45463
+ {
45464
+ accept: "image/*",
45465
+ maxFiles: 1,
45466
+ maxSize: 2 * 1024 * 1024,
45467
+ disabled: isLoading,
45468
+ onFiles: (files) => {
45469
+ const f3 = files[0];
45470
+ if (!f3) return;
45471
+ const reader = new FileReader();
45472
+ reader.onload = () => handleChange(
45473
+ fieldName2,
45474
+ typeof reader.result === "string" ? reader.result : ""
45475
+ );
45476
+ reader.readAsDataURL(f3);
45477
+ }
45478
+ }
45479
+ ),
45480
+ /* @__PURE__ */ jsxRuntime.jsx(
45481
+ exports.Input,
45482
+ {
45483
+ ...commonProps,
45484
+ type: "url",
45485
+ placeholder: t("form.imageUrlFallback"),
45486
+ value: imageUrl,
45487
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45488
+ }
45489
+ )
45490
+ ] });
45491
+ }
45178
45492
  case "date":
45179
45493
  return /* @__PURE__ */ jsxRuntime.jsx(
45180
45494
  exports.Input,
@@ -53941,7 +54255,10 @@ var en_default = {
53941
54255
  "richTextEditor.removeLink": "Remove link",
53942
54256
  "richTextEditor.divider": "Divider",
53943
54257
  "richTextEditor.linkPrompt": "Link address",
53944
- "richTextEditor.placeholder": "Start writing\u2026"
54258
+ "richTextEditor.image": "Insert image",
54259
+ "richTextEditor.imagePrompt": "Image URL",
54260
+ "richTextEditor.placeholder": "Start writing\u2026",
54261
+ "form.imageUrlFallback": "\u2026or paste an image address"
53945
54262
  };
53946
54263
 
53947
54264
  // hooks/useTranslate.ts