@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.
@@ -24954,6 +24954,7 @@ var init_DashboardLayout = __esm({
24954
24954
  onNotificationClick,
24955
24955
  showThemeToggle = true,
24956
24956
  sidebarFooter,
24957
+ sidebarContent,
24957
24958
  onSignOut: onSignOutProp,
24958
24959
  currentPath,
24959
24960
  layoutMode = "sidebar",
@@ -25079,7 +25080,11 @@ var init_DashboardLayout = __esm({
25079
25080
  {
25080
25081
  as: "nav",
25081
25082
  gap: "none",
25082
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
25083
+ className: cn(
25084
+ "py-4 space-y-1 overflow-y-auto",
25085
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
25086
+ isRail ? "px-2" : "px-3"
25087
+ ),
25083
25088
  children: navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
25084
25089
  NavLink,
25085
25090
  {
@@ -25091,6 +25096,7 @@ var init_DashboardLayout = __esm({
25091
25096
  ))
25092
25097
  }
25093
25098
  ),
25099
+ sidebarContent && !isRail && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
25094
25100
  sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
25095
25101
  ]
25096
25102
  }
@@ -27500,7 +27506,17 @@ function fileIcon(name) {
27500
27506
  return "file";
27501
27507
  }
27502
27508
  }
27503
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
27509
+ function ancestorsOf(id, byId) {
27510
+ const out = /* @__PURE__ */ new Set();
27511
+ if (!id) return out;
27512
+ let cur = byId.get(id)?.parentId;
27513
+ while (cur && byId.has(cur) && !out.has(cur)) {
27514
+ out.add(cur);
27515
+ cur = byId.get(cur)?.parentId;
27516
+ }
27517
+ return out;
27518
+ }
27519
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
27504
27520
  var init_FileTree = __esm({
27505
27521
  "components/core/molecules/FileTree.tsx"() {
27506
27522
  "use client";
@@ -27591,42 +27607,77 @@ var init_FileTree = __esm({
27591
27607
  indent,
27592
27608
  childrenByParent,
27593
27609
  onNodeSelect,
27594
- defaultExpanded = false
27610
+ onNodeAction,
27611
+ nodeActionIcon,
27612
+ nodeActionLabel,
27613
+ selectedId,
27614
+ look,
27615
+ isExpanded,
27616
+ onToggle
27595
27617
  }) => {
27596
- const [expanded, setExpanded] = React87.useState(defaultExpanded || depth < 1);
27597
27618
  const children = childrenByParent.get(item.id);
27598
27619
  const hasChildren = !!children && children.length > 0;
27620
+ const expanded = hasChildren && isExpanded(item.id, depth);
27621
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
27622
+ const nav = look === "nav";
27599
27623
  const handleClick = React87.useCallback(() => {
27600
- if (hasChildren) setExpanded((prev) => !prev);
27624
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
27601
27625
  onNodeSelect?.(item.id);
27602
- }, [hasChildren, item.id, onNodeSelect]);
27626
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
27627
+ const handleChevron = React87.useCallback((e) => {
27628
+ e.stopPropagation();
27629
+ onToggle(item.id, expanded);
27630
+ }, [item.id, expanded, onToggle]);
27631
+ const handleAction = React87.useCallback((e) => {
27632
+ e.stopPropagation();
27633
+ onNodeAction?.(item.id);
27634
+ }, [item.id, onNodeAction]);
27603
27635
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
27604
27636
  /* @__PURE__ */ jsxRuntime.jsxs(
27605
27637
  Box,
27606
27638
  {
27607
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27639
+ 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"}`,
27608
27640
  style: { paddingLeft: depth * indent + 8 },
27609
27641
  onClick: handleClick,
27610
27642
  role: "treeitem",
27643
+ "aria-selected": isSelected,
27611
27644
  "aria-expanded": hasChildren ? expanded : void 0,
27612
27645
  children: [
27613
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
27646
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
27614
27647
  Icon,
27615
27648
  {
27616
27649
  name: expanded ? "chevron-down" : "chevron-right",
27617
27650
  size: "xs",
27618
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27651
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
27619
27652
  }
27620
- ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27653
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27621
27654
  /* @__PURE__ */ jsxRuntime.jsx(
27622
27655
  Icon,
27623
27656
  {
27624
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27657
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27625
27658
  size: "xs",
27626
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27659
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
27660
+ }
27661
+ ),
27662
+ /* @__PURE__ */ jsxRuntime.jsx(
27663
+ Typography,
27664
+ {
27665
+ variant: "caption",
27666
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
27667
+ children: item.label
27627
27668
  }
27628
27669
  ),
27629
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27670
+ onNodeAction && /* @__PURE__ */ jsxRuntime.jsx(
27671
+ Box,
27672
+ {
27673
+ onClick: handleAction,
27674
+ role: "button",
27675
+ "aria-label": nodeActionLabel ?? "Node action",
27676
+ title: nodeActionLabel,
27677
+ 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"}`,
27678
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
27679
+ }
27680
+ )
27630
27681
  ]
27631
27682
  }
27632
27683
  ),
@@ -27637,47 +27688,116 @@ var init_FileTree = __esm({
27637
27688
  depth: depth + 1,
27638
27689
  indent,
27639
27690
  childrenByParent,
27640
- onNodeSelect
27691
+ onNodeSelect,
27692
+ onNodeAction,
27693
+ nodeActionIcon,
27694
+ nodeActionLabel,
27695
+ selectedId,
27696
+ look,
27697
+ isExpanded,
27698
+ onToggle
27641
27699
  },
27642
27700
  child.id
27643
27701
  )) })
27644
27702
  ] });
27645
27703
  };
27704
+ FlatFileTree = ({
27705
+ items,
27706
+ selectedId,
27707
+ look = "files",
27708
+ onNodeSelect,
27709
+ onNodeAction,
27710
+ nodeActionIcon,
27711
+ nodeActionLabel,
27712
+ className,
27713
+ indent = 16
27714
+ }) => {
27715
+ const [overrides, setOverrides] = React87.useState(() => /* @__PURE__ */ new Map());
27716
+ const byId = React87__namespace.default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
27717
+ const selectedAncestors = React87__namespace.default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
27718
+ const lastSelectedRef = React87__namespace.default.useRef(selectedId);
27719
+ React87__namespace.default.useEffect(() => {
27720
+ if (selectedId === lastSelectedRef.current) return;
27721
+ lastSelectedRef.current = selectedId;
27722
+ setOverrides((prev) => {
27723
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
27724
+ const next = new Map(prev);
27725
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
27726
+ return next;
27727
+ });
27728
+ }, [selectedId, selectedAncestors]);
27729
+ const isExpanded = React87.useCallback(
27730
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
27731
+ [overrides, selectedAncestors]
27732
+ );
27733
+ const onToggle = React87.useCallback((id, effective) => {
27734
+ setOverrides((prev) => {
27735
+ const next = new Map(prev);
27736
+ next.set(id, !effective);
27737
+ return next;
27738
+ });
27739
+ }, []);
27740
+ if (items.length === 0) return null;
27741
+ const ids = new Set(items.map((node) => node.id));
27742
+ const childrenByParent = /* @__PURE__ */ new Map();
27743
+ const roots = [];
27744
+ for (const item of items) {
27745
+ if (item.parentId && ids.has(item.parentId)) {
27746
+ const siblings = childrenByParent.get(item.parentId);
27747
+ if (siblings) siblings.push(item);
27748
+ else childrenByParent.set(item.parentId, [item]);
27749
+ } else {
27750
+ roots.push(item);
27751
+ }
27752
+ }
27753
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27754
+ FlatTreeNodeItem,
27755
+ {
27756
+ item,
27757
+ depth: 0,
27758
+ indent,
27759
+ childrenByParent,
27760
+ onNodeSelect,
27761
+ onNodeAction,
27762
+ nodeActionIcon,
27763
+ nodeActionLabel,
27764
+ selectedId,
27765
+ look,
27766
+ isExpanded,
27767
+ onToggle
27768
+ },
27769
+ item.id
27770
+ )) });
27771
+ };
27646
27772
  FileTree = ({
27647
27773
  tree,
27648
27774
  items,
27649
27775
  selectedPath,
27776
+ selectedId,
27777
+ look = "files",
27650
27778
  onFileSelect,
27651
27779
  onNodeSelect,
27780
+ onNodeAction,
27781
+ nodeActionIcon,
27782
+ nodeActionLabel,
27652
27783
  className,
27653
27784
  indent = 16
27654
27785
  }) => {
27655
27786
  if (items) {
27656
- if (items.length === 0) return null;
27657
- const ids = new Set(items.map((node) => node.id));
27658
- const childrenByParent = /* @__PURE__ */ new Map();
27659
- const roots = [];
27660
- for (const item of items) {
27661
- if (item.parentId && ids.has(item.parentId)) {
27662
- const siblings = childrenByParent.get(item.parentId);
27663
- if (siblings) siblings.push(item);
27664
- else childrenByParent.set(item.parentId, [item]);
27665
- } else {
27666
- roots.push(item);
27667
- }
27668
- }
27669
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27670
- FlatTreeNodeItem,
27787
+ return /* @__PURE__ */ jsxRuntime.jsx(
27788
+ FlatFileTree,
27671
27789
  {
27672
- item,
27673
- depth: 0,
27674
- indent,
27675
- childrenByParent,
27790
+ items,
27791
+ selectedId,
27792
+ look,
27676
27793
  onNodeSelect,
27677
- defaultExpanded: true
27678
- },
27679
- item.id
27680
- )) });
27794
+ onNodeAction,
27795
+ nodeActionIcon,
27796
+ nodeActionLabel,
27797
+ className,
27798
+ indent
27799
+ }
27800
+ );
27681
27801
  }
27682
27802
  if (!tree || tree.length === 0) return null;
27683
27803
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -37121,13 +37241,35 @@ var init_RichTextEditor = __esm({
37121
37241
  document.execCommand("createLink", false, safe);
37122
37242
  afterEdit();
37123
37243
  }, [afterEdit, t, toolbar.link]);
37244
+ const imageInputRef = React87.useRef(null);
37245
+ const savedRangeRef = React87.useRef(null);
37124
37246
  const execImage = React87.useCallback(() => {
37125
- const url = window.prompt(t("richTextEditor.imagePrompt"));
37126
- if (!url) return;
37127
- const safe = safeUrl(url, ["http://", "https://", "data:image/"]) ?? `https://${url}`;
37128
- document.execCommand("insertImage", false, safe);
37129
- afterEdit();
37130
- }, [afterEdit, t]);
37247
+ const root = ref.current;
37248
+ const sel = window.getSelection();
37249
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
37250
+ imageInputRef.current?.click();
37251
+ }, []);
37252
+ const handleImageFile = React87.useCallback((e) => {
37253
+ const file = e.target.files?.[0];
37254
+ e.target.value = "";
37255
+ if (!file) return;
37256
+ const reader = new FileReader();
37257
+ reader.onload = () => {
37258
+ const url = typeof reader.result === "string" ? reader.result : "";
37259
+ if (!url.startsWith("data:image/")) return;
37260
+ const root = ref.current;
37261
+ if (!root) return;
37262
+ root.focus();
37263
+ const sel = window.getSelection();
37264
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
37265
+ sel.removeAllRanges();
37266
+ sel.addRange(savedRangeRef.current);
37267
+ }
37268
+ document.execCommand("insertImage", false, url);
37269
+ afterEdit();
37270
+ };
37271
+ reader.readAsDataURL(file);
37272
+ }, [afterEdit]);
37131
37273
  const execRule = React87.useCallback(() => {
37132
37274
  document.execCommand("insertHorizontalRule", false);
37133
37275
  afterEdit();
@@ -37140,6 +37282,18 @@ var init_RichTextEditor = __esm({
37140
37282
  }
37141
37283
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
37142
37284
  /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
37285
+ /* @__PURE__ */ jsxRuntime.jsx(
37286
+ "input",
37287
+ {
37288
+ ref: imageInputRef,
37289
+ type: "file",
37290
+ accept: "image/*",
37291
+ className: "hidden",
37292
+ "aria-hidden": "true",
37293
+ tabIndex: -1,
37294
+ onChange: handleImageFile
37295
+ }
37296
+ ),
37143
37297
  showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
37144
37298
  Box,
37145
37299
  {
@@ -44039,15 +44193,6 @@ var init_Form = __esm({
44039
44193
  "data-testid": `image-preview-${fieldName2}`
44040
44194
  }
44041
44195
  ) : null,
44042
- /* @__PURE__ */ jsxRuntime.jsx(
44043
- Input,
44044
- {
44045
- ...commonProps,
44046
- type: "url",
44047
- value: imageUrl,
44048
- onChange: (e) => handleChange(fieldName2, e.target.value)
44049
- }
44050
- ),
44051
44196
  /* @__PURE__ */ jsxRuntime.jsx(
44052
44197
  UploadDropZone,
44053
44198
  {
@@ -44066,6 +44211,16 @@ var init_Form = __esm({
44066
44211
  reader.readAsDataURL(f3);
44067
44212
  }
44068
44213
  }
44214
+ ),
44215
+ /* @__PURE__ */ jsxRuntime.jsx(
44216
+ Input,
44217
+ {
44218
+ ...commonProps,
44219
+ type: "url",
44220
+ placeholder: t("form.imageUrlFallback"),
44221
+ value: imageUrl,
44222
+ onChange: (e) => handleChange(fieldName2, e.target.value)
44223
+ }
44069
44224
  )
44070
44225
  ] });
44071
44226
  }