@almadar/ui 5.163.0 → 5.165.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
  }
@@ -25651,6 +25657,8 @@ function FieldControl({
25651
25657
  control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
25652
25658
  } else if (core.isSecretConfigType(decl.type)) {
25653
25659
  control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, secret: true, value, onCommit: onChange });
25660
+ } else if (decl.type === "event") {
25661
+ control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
25654
25662
  } else if (decl.type === "string") {
25655
25663
  control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
25656
25664
  } else if (decl.type === "node") {
@@ -25690,7 +25698,7 @@ var init_PropertyInspector = __esm({
25690
25698
  init_JsonTreeEditor();
25691
25699
  init_NodeSlotEditor();
25692
25700
  TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
25693
- SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset", "secret"]);
25701
+ SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset", "secret", "event"]);
25694
25702
  exports.PropertyInspector = ({
25695
25703
  config,
25696
25704
  values,
@@ -25848,7 +25856,17 @@ function fileIcon(name) {
25848
25856
  return "file";
25849
25857
  }
25850
25858
  }
25851
- var TreeNodeItem, FlatTreeNodeItem; exports.FileTree = void 0;
25859
+ function ancestorsOf(id, byId) {
25860
+ const out = /* @__PURE__ */ new Set();
25861
+ if (!id) return out;
25862
+ let cur = byId.get(id)?.parentId;
25863
+ while (cur && byId.has(cur) && !out.has(cur)) {
25864
+ out.add(cur);
25865
+ cur = byId.get(cur)?.parentId;
25866
+ }
25867
+ return out;
25868
+ }
25869
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree; exports.FileTree = void 0;
25852
25870
  var init_FileTree = __esm({
25853
25871
  "components/core/molecules/FileTree.tsx"() {
25854
25872
  "use client";
@@ -25939,42 +25957,77 @@ var init_FileTree = __esm({
25939
25957
  indent,
25940
25958
  childrenByParent,
25941
25959
  onNodeSelect,
25942
- defaultExpanded = false
25960
+ onNodeAction,
25961
+ nodeActionIcon,
25962
+ nodeActionLabel,
25963
+ selectedId,
25964
+ look,
25965
+ isExpanded,
25966
+ onToggle
25943
25967
  }) => {
25944
- const [expanded, setExpanded] = React77.useState(defaultExpanded || depth < 1);
25945
25968
  const children = childrenByParent.get(item.id);
25946
25969
  const hasChildren = !!children && children.length > 0;
25970
+ const expanded = hasChildren && isExpanded(item.id, depth);
25971
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
25972
+ const nav = look === "nav";
25947
25973
  const handleClick = React77.useCallback(() => {
25948
- if (hasChildren) setExpanded((prev) => !prev);
25974
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
25949
25975
  onNodeSelect?.(item.id);
25950
- }, [hasChildren, item.id, onNodeSelect]);
25976
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
25977
+ const handleChevron = React77.useCallback((e) => {
25978
+ e.stopPropagation();
25979
+ onToggle(item.id, expanded);
25980
+ }, [item.id, expanded, onToggle]);
25981
+ const handleAction = React77.useCallback((e) => {
25982
+ e.stopPropagation();
25983
+ onNodeAction?.(item.id);
25984
+ }, [item.id, onNodeAction]);
25951
25985
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
25952
25986
  /* @__PURE__ */ jsxRuntime.jsxs(
25953
25987
  exports.Box,
25954
25988
  {
25955
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
25989
+ 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
25990
  style: { paddingLeft: depth * indent + 8 },
25957
25991
  onClick: handleClick,
25958
25992
  role: "treeitem",
25993
+ "aria-selected": isSelected,
25959
25994
  "aria-expanded": hasChildren ? expanded : void 0,
25960
25995
  children: [
25961
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
25996
+ 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
25997
  exports.Icon,
25963
25998
  {
25964
25999
  name: expanded ? "chevron-down" : "chevron-right",
25965
26000
  size: "xs",
25966
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
26001
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
25967
26002
  }
25968
- ) : /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { style: { width: 12, flexShrink: 0 } }),
26003
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { style: { width: 12, flexShrink: 0 } }),
25969
26004
  /* @__PURE__ */ jsxRuntime.jsx(
25970
26005
  exports.Icon,
25971
26006
  {
25972
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
26007
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25973
26008
  size: "xs",
25974
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
26009
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
26010
+ }
26011
+ ),
26012
+ /* @__PURE__ */ jsxRuntime.jsx(
26013
+ exports.Typography,
26014
+ {
26015
+ variant: "caption",
26016
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
26017
+ children: item.label
25975
26018
  }
25976
26019
  ),
25977
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
26020
+ onNodeAction && /* @__PURE__ */ jsxRuntime.jsx(
26021
+ exports.Box,
26022
+ {
26023
+ onClick: handleAction,
26024
+ role: "button",
26025
+ "aria-label": nodeActionLabel ?? "Node action",
26026
+ title: nodeActionLabel,
26027
+ 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"}`,
26028
+ children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
26029
+ }
26030
+ )
25978
26031
  ]
25979
26032
  }
25980
26033
  ),
@@ -25985,47 +26038,116 @@ var init_FileTree = __esm({
25985
26038
  depth: depth + 1,
25986
26039
  indent,
25987
26040
  childrenByParent,
25988
- onNodeSelect
26041
+ onNodeSelect,
26042
+ onNodeAction,
26043
+ nodeActionIcon,
26044
+ nodeActionLabel,
26045
+ selectedId,
26046
+ look,
26047
+ isExpanded,
26048
+ onToggle
25989
26049
  },
25990
26050
  child.id
25991
26051
  )) })
25992
26052
  ] });
25993
26053
  };
26054
+ FlatFileTree = ({
26055
+ items,
26056
+ selectedId,
26057
+ look = "files",
26058
+ onNodeSelect,
26059
+ onNodeAction,
26060
+ nodeActionIcon,
26061
+ nodeActionLabel,
26062
+ className,
26063
+ indent = 16
26064
+ }) => {
26065
+ const [overrides, setOverrides] = React77.useState(() => /* @__PURE__ */ new Map());
26066
+ const byId = React77__namespace.default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
26067
+ const selectedAncestors = React77__namespace.default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
26068
+ const lastSelectedRef = React77__namespace.default.useRef(selectedId);
26069
+ React77__namespace.default.useEffect(() => {
26070
+ if (selectedId === lastSelectedRef.current) return;
26071
+ lastSelectedRef.current = selectedId;
26072
+ setOverrides((prev) => {
26073
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
26074
+ const next = new Map(prev);
26075
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
26076
+ return next;
26077
+ });
26078
+ }, [selectedId, selectedAncestors]);
26079
+ const isExpanded = React77.useCallback(
26080
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
26081
+ [overrides, selectedAncestors]
26082
+ );
26083
+ const onToggle = React77.useCallback((id, effective) => {
26084
+ setOverrides((prev) => {
26085
+ const next = new Map(prev);
26086
+ next.set(id, !effective);
26087
+ return next;
26088
+ });
26089
+ }, []);
26090
+ if (items.length === 0) return null;
26091
+ const ids = new Set(items.map((node) => node.id));
26092
+ const childrenByParent = /* @__PURE__ */ new Map();
26093
+ const roots = [];
26094
+ for (const item of items) {
26095
+ if (item.parentId && ids.has(item.parentId)) {
26096
+ const siblings = childrenByParent.get(item.parentId);
26097
+ if (siblings) siblings.push(item);
26098
+ else childrenByParent.set(item.parentId, [item]);
26099
+ } else {
26100
+ roots.push(item);
26101
+ }
26102
+ }
26103
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
26104
+ FlatTreeNodeItem,
26105
+ {
26106
+ item,
26107
+ depth: 0,
26108
+ indent,
26109
+ childrenByParent,
26110
+ onNodeSelect,
26111
+ onNodeAction,
26112
+ nodeActionIcon,
26113
+ nodeActionLabel,
26114
+ selectedId,
26115
+ look,
26116
+ isExpanded,
26117
+ onToggle
26118
+ },
26119
+ item.id
26120
+ )) });
26121
+ };
25994
26122
  exports.FileTree = ({
25995
26123
  tree,
25996
26124
  items,
25997
26125
  selectedPath,
26126
+ selectedId,
26127
+ look = "files",
25998
26128
  onFileSelect,
25999
26129
  onNodeSelect,
26130
+ onNodeAction,
26131
+ nodeActionIcon,
26132
+ nodeActionLabel,
26000
26133
  className,
26001
26134
  indent = 16
26002
26135
  }) => {
26003
26136
  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,
26137
+ return /* @__PURE__ */ jsxRuntime.jsx(
26138
+ FlatFileTree,
26019
26139
  {
26020
- item,
26021
- depth: 0,
26022
- indent,
26023
- childrenByParent,
26140
+ items,
26141
+ selectedId,
26142
+ look,
26024
26143
  onNodeSelect,
26025
- defaultExpanded: true
26026
- },
26027
- item.id
26028
- )) });
26144
+ onNodeAction,
26145
+ nodeActionIcon,
26146
+ nodeActionLabel,
26147
+ className,
26148
+ indent
26149
+ }
26150
+ );
26029
26151
  }
26030
26152
  if (!tree || tree.length === 0) return null;
26031
26153
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -38159,13 +38281,35 @@ var init_RichTextEditor = __esm({
38159
38281
  document.execCommand("createLink", false, safe);
38160
38282
  afterEdit();
38161
38283
  }, [afterEdit, t, toolbar.link]);
38284
+ const imageInputRef = React77.useRef(null);
38285
+ const savedRangeRef = React77.useRef(null);
38162
38286
  const execImage = React77.useCallback(() => {
38163
- const url = window.prompt(t("richTextEditor.imagePrompt"));
38164
- if (!url) return;
38165
- const safe = safeUrl(url, ["http://", "https://", "data:image/"]) ?? `https://${url}`;
38166
- document.execCommand("insertImage", false, safe);
38167
- afterEdit();
38168
- }, [afterEdit, t]);
38287
+ const root = ref.current;
38288
+ const sel = window.getSelection();
38289
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
38290
+ imageInputRef.current?.click();
38291
+ }, []);
38292
+ const handleImageFile = React77.useCallback((e) => {
38293
+ const file = e.target.files?.[0];
38294
+ e.target.value = "";
38295
+ if (!file) return;
38296
+ const reader = new FileReader();
38297
+ reader.onload = () => {
38298
+ const url = typeof reader.result === "string" ? reader.result : "";
38299
+ if (!url.startsWith("data:image/")) return;
38300
+ const root = ref.current;
38301
+ if (!root) return;
38302
+ root.focus();
38303
+ const sel = window.getSelection();
38304
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
38305
+ sel.removeAllRanges();
38306
+ sel.addRange(savedRangeRef.current);
38307
+ }
38308
+ document.execCommand("insertImage", false, url);
38309
+ afterEdit();
38310
+ };
38311
+ reader.readAsDataURL(file);
38312
+ }, [afterEdit]);
38169
38313
  const execRule = React77.useCallback(() => {
38170
38314
  document.execCommand("insertHorizontalRule", false);
38171
38315
  afterEdit();
@@ -38178,6 +38322,18 @@ var init_RichTextEditor = __esm({
38178
38322
  }
38179
38323
  return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: cn("flex flex-col gap-2", className), children: [
38180
38324
  /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
38325
+ /* @__PURE__ */ jsxRuntime.jsx(
38326
+ "input",
38327
+ {
38328
+ ref: imageInputRef,
38329
+ type: "file",
38330
+ accept: "image/*",
38331
+ className: "hidden",
38332
+ "aria-hidden": "true",
38333
+ tabIndex: -1,
38334
+ onChange: handleImageFile
38335
+ }
38336
+ ),
38181
38337
  showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
38182
38338
  exports.Box,
38183
38339
  {
@@ -45304,15 +45460,6 @@ var init_Form = __esm({
45304
45460
  "data-testid": `image-preview-${fieldName2}`
45305
45461
  }
45306
45462
  ) : null,
45307
- /* @__PURE__ */ jsxRuntime.jsx(
45308
- exports.Input,
45309
- {
45310
- ...commonProps,
45311
- type: "url",
45312
- value: imageUrl,
45313
- onChange: (e) => handleChange(fieldName2, e.target.value)
45314
- }
45315
- ),
45316
45463
  /* @__PURE__ */ jsxRuntime.jsx(
45317
45464
  exports.UploadDropZone,
45318
45465
  {
@@ -45331,6 +45478,16 @@ var init_Form = __esm({
45331
45478
  reader.readAsDataURL(f3);
45332
45479
  }
45333
45480
  }
45481
+ ),
45482
+ /* @__PURE__ */ jsxRuntime.jsx(
45483
+ exports.Input,
45484
+ {
45485
+ ...commonProps,
45486
+ type: "url",
45487
+ placeholder: t("form.imageUrlFallback"),
45488
+ value: imageUrl,
45489
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45490
+ }
45334
45491
  )
45335
45492
  ] });
45336
45493
  }
@@ -54102,7 +54259,8 @@ var en_default = {
54102
54259
  "richTextEditor.linkPrompt": "Link address",
54103
54260
  "richTextEditor.image": "Insert image",
54104
54261
  "richTextEditor.imagePrompt": "Image URL",
54105
- "richTextEditor.placeholder": "Start writing\u2026"
54262
+ "richTextEditor.placeholder": "Start writing\u2026",
54263
+ "form.imageUrlFallback": "\u2026or paste an image address"
54106
54264
  };
54107
54265
 
54108
54266
  // hooks/useTranslate.ts
@@ -3755,6 +3755,14 @@ interface FileTreeProps {
3755
3755
  items?: FileTreeItem[];
3756
3756
  /** Currently selected file path */
3757
3757
  selectedPath?: string;
3758
+ /** Currently selected node id, in flat `items` mode: the row highlights
3759
+ * and every ancestor auto-expands so the selection is always visible —
3760
+ * the "you are here" a navigation tree needs. */
3761
+ selectedId?: string;
3762
+ /** Visual treatment for flat `items` mode: `files` (default) keeps the
3763
+ * monospace file-browser look; `nav` renders a sidebar navigation tree —
3764
+ * regular font, document icons, roomier hit areas. */
3765
+ look?: 'files' | 'nav';
3758
3766
  /** Called when a file is clicked */
3759
3767
  onFileSelect?: (path: string) => void;
3760
3768
  /** Called when a node is clicked, in flat `items` mode; carries the
@@ -3762,6 +3770,14 @@ interface FileTreeProps {
3762
3770
  * the click-to-select affordance, mirroring Badge's `onRemove`.
3763
3771
  * @offByDefault */
3764
3772
  onNodeSelect?: (id: string) => void;
3773
+ /** Called from the row's hover action button, in flat `items` mode;
3774
+ * carries that node's id. Presence-gated: binding it renders the action.
3775
+ * @offByDefault */
3776
+ onNodeAction?: (id: string) => void;
3777
+ /** Icon for the row hover action (default: plus). */
3778
+ nodeActionIcon?: string;
3779
+ /** Accessible label/tooltip for the row hover action. */
3780
+ nodeActionLabel?: string;
3765
3781
  /** CSS class */
3766
3782
  className?: string;
3767
3783
  /** Indent size per level in px (default: 16) */
@@ -12650,6 +12666,10 @@ interface DashboardLayoutProps {
12650
12666
  /** Custom sidebar footer (optional). When omitted, the sidebar has
12651
12667
  * no footer — apps that need Settings/etc. add them via navItems. */
12652
12668
  sidebarFooter?: React__default.ReactNode;
12669
+ /** Optional content region rendered between the nav links and the footer —
12670
+ * a page tree or any consumer navigation surface; scrolls independently
12671
+ * and takes the sidebar's remaining height. Hidden in the collapsed rail. */
12672
+ sidebarContent?: React__default.ReactNode;
12653
12673
  /** Active path used to highlight the matching nav item. Falls back
12654
12674
  * to `useCurrentPagePath()` (set by `CurrentPagePathProvider`), then
12655
12675
  * to `useLocation().pathname`. Production deploys can omit it; the
@@ -3755,6 +3755,14 @@ interface FileTreeProps {
3755
3755
  items?: FileTreeItem[];
3756
3756
  /** Currently selected file path */
3757
3757
  selectedPath?: string;
3758
+ /** Currently selected node id, in flat `items` mode: the row highlights
3759
+ * and every ancestor auto-expands so the selection is always visible —
3760
+ * the "you are here" a navigation tree needs. */
3761
+ selectedId?: string;
3762
+ /** Visual treatment for flat `items` mode: `files` (default) keeps the
3763
+ * monospace file-browser look; `nav` renders a sidebar navigation tree —
3764
+ * regular font, document icons, roomier hit areas. */
3765
+ look?: 'files' | 'nav';
3758
3766
  /** Called when a file is clicked */
3759
3767
  onFileSelect?: (path: string) => void;
3760
3768
  /** Called when a node is clicked, in flat `items` mode; carries the
@@ -3762,6 +3770,14 @@ interface FileTreeProps {
3762
3770
  * the click-to-select affordance, mirroring Badge's `onRemove`.
3763
3771
  * @offByDefault */
3764
3772
  onNodeSelect?: (id: string) => void;
3773
+ /** Called from the row's hover action button, in flat `items` mode;
3774
+ * carries that node's id. Presence-gated: binding it renders the action.
3775
+ * @offByDefault */
3776
+ onNodeAction?: (id: string) => void;
3777
+ /** Icon for the row hover action (default: plus). */
3778
+ nodeActionIcon?: string;
3779
+ /** Accessible label/tooltip for the row hover action. */
3780
+ nodeActionLabel?: string;
3765
3781
  /** CSS class */
3766
3782
  className?: string;
3767
3783
  /** Indent size per level in px (default: 16) */
@@ -12650,6 +12666,10 @@ interface DashboardLayoutProps {
12650
12666
  /** Custom sidebar footer (optional). When omitted, the sidebar has
12651
12667
  * no footer — apps that need Settings/etc. add them via navItems. */
12652
12668
  sidebarFooter?: React__default.ReactNode;
12669
+ /** Optional content region rendered between the nav links and the footer —
12670
+ * a page tree or any consumer navigation surface; scrolls independently
12671
+ * and takes the sidebar's remaining height. Hidden in the collapsed rail. */
12672
+ sidebarContent?: React__default.ReactNode;
12653
12673
  /** Active path used to highlight the matching nav item. Falls back
12654
12674
  * to `useCurrentPagePath()` (set by `CurrentPagePathProvider`), then
12655
12675
  * to `useLocation().pathname`. Production deploys can omit it; the