@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.
@@ -27228,6 +27228,7 @@ var init_DashboardLayout = __esm({
27228
27228
  onNotificationClick,
27229
27229
  showThemeToggle = true,
27230
27230
  sidebarFooter,
27231
+ sidebarContent,
27231
27232
  onSignOut: onSignOutProp,
27232
27233
  currentPath,
27233
27234
  layoutMode = "sidebar",
@@ -27353,7 +27354,11 @@ var init_DashboardLayout = __esm({
27353
27354
  {
27354
27355
  as: "nav",
27355
27356
  gap: "none",
27356
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
27357
+ className: cn(
27358
+ "py-4 space-y-1 overflow-y-auto",
27359
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
27360
+ isRail ? "px-2" : "px-3"
27361
+ ),
27357
27362
  children: navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27358
27363
  NavLink,
27359
27364
  {
@@ -27365,6 +27370,7 @@ var init_DashboardLayout = __esm({
27365
27370
  ))
27366
27371
  }
27367
27372
  ),
27373
+ 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 }),
27368
27374
  sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
27369
27375
  ]
27370
27376
  }
@@ -29774,7 +29780,17 @@ function fileIcon(name) {
29774
29780
  return "file";
29775
29781
  }
29776
29782
  }
29777
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
29783
+ function ancestorsOf(id, byId) {
29784
+ const out = /* @__PURE__ */ new Set();
29785
+ if (!id) return out;
29786
+ let cur = byId.get(id)?.parentId;
29787
+ while (cur && byId.has(cur) && !out.has(cur)) {
29788
+ out.add(cur);
29789
+ cur = byId.get(cur)?.parentId;
29790
+ }
29791
+ return out;
29792
+ }
29793
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
29778
29794
  var init_FileTree = __esm({
29779
29795
  "components/core/molecules/FileTree.tsx"() {
29780
29796
  "use client";
@@ -29865,42 +29881,77 @@ var init_FileTree = __esm({
29865
29881
  indent,
29866
29882
  childrenByParent,
29867
29883
  onNodeSelect,
29868
- defaultExpanded = false
29884
+ onNodeAction,
29885
+ nodeActionIcon,
29886
+ nodeActionLabel,
29887
+ selectedId,
29888
+ look,
29889
+ isExpanded,
29890
+ onToggle
29869
29891
  }) => {
29870
- const [expanded, setExpanded] = React94.useState(defaultExpanded || depth < 1);
29871
29892
  const children = childrenByParent.get(item.id);
29872
29893
  const hasChildren = !!children && children.length > 0;
29894
+ const expanded = hasChildren && isExpanded(item.id, depth);
29895
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
29896
+ const nav = look === "nav";
29873
29897
  const handleClick = React94.useCallback(() => {
29874
- if (hasChildren) setExpanded((prev) => !prev);
29898
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
29875
29899
  onNodeSelect?.(item.id);
29876
- }, [hasChildren, item.id, onNodeSelect]);
29900
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
29901
+ const handleChevron = React94.useCallback((e) => {
29902
+ e.stopPropagation();
29903
+ onToggle(item.id, expanded);
29904
+ }, [item.id, expanded, onToggle]);
29905
+ const handleAction = React94.useCallback((e) => {
29906
+ e.stopPropagation();
29907
+ onNodeAction?.(item.id);
29908
+ }, [item.id, onNodeAction]);
29877
29909
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29878
29910
  /* @__PURE__ */ jsxRuntime.jsxs(
29879
29911
  Box,
29880
29912
  {
29881
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
29913
+ 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"}`,
29882
29914
  style: { paddingLeft: depth * indent + 8 },
29883
29915
  onClick: handleClick,
29884
29916
  role: "treeitem",
29917
+ "aria-selected": isSelected,
29885
29918
  "aria-expanded": hasChildren ? expanded : void 0,
29886
29919
  children: [
29887
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
29920
+ 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(
29888
29921
  Icon,
29889
29922
  {
29890
29923
  name: expanded ? "chevron-down" : "chevron-right",
29891
29924
  size: "xs",
29892
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
29925
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
29893
29926
  }
29894
- ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29927
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29895
29928
  /* @__PURE__ */ jsxRuntime.jsx(
29896
29929
  Icon,
29897
29930
  {
29898
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29931
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29899
29932
  size: "xs",
29900
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
29933
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
29934
+ }
29935
+ ),
29936
+ /* @__PURE__ */ jsxRuntime.jsx(
29937
+ Typography,
29938
+ {
29939
+ variant: "caption",
29940
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
29941
+ children: item.label
29901
29942
  }
29902
29943
  ),
29903
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
29944
+ onNodeAction && /* @__PURE__ */ jsxRuntime.jsx(
29945
+ Box,
29946
+ {
29947
+ onClick: handleAction,
29948
+ role: "button",
29949
+ "aria-label": nodeActionLabel ?? "Node action",
29950
+ title: nodeActionLabel,
29951
+ 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"}`,
29952
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
29953
+ }
29954
+ )
29904
29955
  ]
29905
29956
  }
29906
29957
  ),
@@ -29911,47 +29962,116 @@ var init_FileTree = __esm({
29911
29962
  depth: depth + 1,
29912
29963
  indent,
29913
29964
  childrenByParent,
29914
- onNodeSelect
29965
+ onNodeSelect,
29966
+ onNodeAction,
29967
+ nodeActionIcon,
29968
+ nodeActionLabel,
29969
+ selectedId,
29970
+ look,
29971
+ isExpanded,
29972
+ onToggle
29915
29973
  },
29916
29974
  child.id
29917
29975
  )) })
29918
29976
  ] });
29919
29977
  };
29978
+ FlatFileTree = ({
29979
+ items,
29980
+ selectedId,
29981
+ look = "files",
29982
+ onNodeSelect,
29983
+ onNodeAction,
29984
+ nodeActionIcon,
29985
+ nodeActionLabel,
29986
+ className,
29987
+ indent = 16
29988
+ }) => {
29989
+ const [overrides, setOverrides] = React94.useState(() => /* @__PURE__ */ new Map());
29990
+ const byId = React94__namespace.default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
29991
+ const selectedAncestors = React94__namespace.default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
29992
+ const lastSelectedRef = React94__namespace.default.useRef(selectedId);
29993
+ React94__namespace.default.useEffect(() => {
29994
+ if (selectedId === lastSelectedRef.current) return;
29995
+ lastSelectedRef.current = selectedId;
29996
+ setOverrides((prev) => {
29997
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
29998
+ const next = new Map(prev);
29999
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
30000
+ return next;
30001
+ });
30002
+ }, [selectedId, selectedAncestors]);
30003
+ const isExpanded = React94.useCallback(
30004
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
30005
+ [overrides, selectedAncestors]
30006
+ );
30007
+ const onToggle = React94.useCallback((id, effective) => {
30008
+ setOverrides((prev) => {
30009
+ const next = new Map(prev);
30010
+ next.set(id, !effective);
30011
+ return next;
30012
+ });
30013
+ }, []);
30014
+ if (items.length === 0) return null;
30015
+ const ids = new Set(items.map((node) => node.id));
30016
+ const childrenByParent = /* @__PURE__ */ new Map();
30017
+ const roots = [];
30018
+ for (const item of items) {
30019
+ if (item.parentId && ids.has(item.parentId)) {
30020
+ const siblings = childrenByParent.get(item.parentId);
30021
+ if (siblings) siblings.push(item);
30022
+ else childrenByParent.set(item.parentId, [item]);
30023
+ } else {
30024
+ roots.push(item);
30025
+ }
30026
+ }
30027
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
30028
+ FlatTreeNodeItem,
30029
+ {
30030
+ item,
30031
+ depth: 0,
30032
+ indent,
30033
+ childrenByParent,
30034
+ onNodeSelect,
30035
+ onNodeAction,
30036
+ nodeActionIcon,
30037
+ nodeActionLabel,
30038
+ selectedId,
30039
+ look,
30040
+ isExpanded,
30041
+ onToggle
30042
+ },
30043
+ item.id
30044
+ )) });
30045
+ };
29920
30046
  FileTree = ({
29921
30047
  tree,
29922
30048
  items,
29923
30049
  selectedPath,
30050
+ selectedId,
30051
+ look = "files",
29924
30052
  onFileSelect,
29925
30053
  onNodeSelect,
30054
+ onNodeAction,
30055
+ nodeActionIcon,
30056
+ nodeActionLabel,
29926
30057
  className,
29927
30058
  indent = 16
29928
30059
  }) => {
29929
30060
  if (items) {
29930
- if (items.length === 0) return null;
29931
- const ids = new Set(items.map((node) => node.id));
29932
- const childrenByParent = /* @__PURE__ */ new Map();
29933
- const roots = [];
29934
- for (const item of items) {
29935
- if (item.parentId && ids.has(item.parentId)) {
29936
- const siblings = childrenByParent.get(item.parentId);
29937
- if (siblings) siblings.push(item);
29938
- else childrenByParent.set(item.parentId, [item]);
29939
- } else {
29940
- roots.push(item);
29941
- }
29942
- }
29943
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
29944
- FlatTreeNodeItem,
30061
+ return /* @__PURE__ */ jsxRuntime.jsx(
30062
+ FlatFileTree,
29945
30063
  {
29946
- item,
29947
- depth: 0,
29948
- indent,
29949
- childrenByParent,
30064
+ items,
30065
+ selectedId,
30066
+ look,
29950
30067
  onNodeSelect,
29951
- defaultExpanded: true
29952
- },
29953
- item.id
29954
- )) });
30068
+ onNodeAction,
30069
+ nodeActionIcon,
30070
+ nodeActionLabel,
30071
+ className,
30072
+ indent
30073
+ }
30074
+ );
29955
30075
  }
29956
30076
  if (!tree || tree.length === 0) return null;
29957
30077
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -39395,13 +39515,35 @@ var init_RichTextEditor = __esm({
39395
39515
  document.execCommand("createLink", false, safe);
39396
39516
  afterEdit();
39397
39517
  }, [afterEdit, t, toolbar.link]);
39518
+ const imageInputRef = React94.useRef(null);
39519
+ const savedRangeRef = React94.useRef(null);
39398
39520
  const execImage = React94.useCallback(() => {
39399
- const url = window.prompt(t("richTextEditor.imagePrompt"));
39400
- if (!url) return;
39401
- const safe = safeUrl(url, ["http://", "https://", "data:image/"]) ?? `https://${url}`;
39402
- document.execCommand("insertImage", false, safe);
39403
- afterEdit();
39404
- }, [afterEdit, t]);
39521
+ const root = ref.current;
39522
+ const sel = window.getSelection();
39523
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
39524
+ imageInputRef.current?.click();
39525
+ }, []);
39526
+ const handleImageFile = React94.useCallback((e) => {
39527
+ const file = e.target.files?.[0];
39528
+ e.target.value = "";
39529
+ if (!file) return;
39530
+ const reader = new FileReader();
39531
+ reader.onload = () => {
39532
+ const url = typeof reader.result === "string" ? reader.result : "";
39533
+ if (!url.startsWith("data:image/")) return;
39534
+ const root = ref.current;
39535
+ if (!root) return;
39536
+ root.focus();
39537
+ const sel = window.getSelection();
39538
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
39539
+ sel.removeAllRanges();
39540
+ sel.addRange(savedRangeRef.current);
39541
+ }
39542
+ document.execCommand("insertImage", false, url);
39543
+ afterEdit();
39544
+ };
39545
+ reader.readAsDataURL(file);
39546
+ }, [afterEdit]);
39405
39547
  const execRule = React94.useCallback(() => {
39406
39548
  document.execCommand("insertHorizontalRule", false);
39407
39549
  afterEdit();
@@ -39414,6 +39556,18 @@ var init_RichTextEditor = __esm({
39414
39556
  }
39415
39557
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
39416
39558
  /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
39559
+ /* @__PURE__ */ jsxRuntime.jsx(
39560
+ "input",
39561
+ {
39562
+ ref: imageInputRef,
39563
+ type: "file",
39564
+ accept: "image/*",
39565
+ className: "hidden",
39566
+ "aria-hidden": "true",
39567
+ tabIndex: -1,
39568
+ onChange: handleImageFile
39569
+ }
39570
+ ),
39417
39571
  showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
39418
39572
  Box,
39419
39573
  {
@@ -45904,15 +46058,6 @@ var init_Form = __esm({
45904
46058
  "data-testid": `image-preview-${fieldName2}`
45905
46059
  }
45906
46060
  ) : null,
45907
- /* @__PURE__ */ jsxRuntime.jsx(
45908
- Input,
45909
- {
45910
- ...commonProps,
45911
- type: "url",
45912
- value: imageUrl,
45913
- onChange: (e) => handleChange(fieldName2, e.target.value)
45914
- }
45915
- ),
45916
46061
  /* @__PURE__ */ jsxRuntime.jsx(
45917
46062
  UploadDropZone,
45918
46063
  {
@@ -45931,6 +46076,16 @@ var init_Form = __esm({
45931
46076
  reader.readAsDataURL(f3);
45932
46077
  }
45933
46078
  }
46079
+ ),
46080
+ /* @__PURE__ */ jsxRuntime.jsx(
46081
+ Input,
46082
+ {
46083
+ ...commonProps,
46084
+ type: "url",
46085
+ placeholder: t("form.imageUrlFallback"),
46086
+ value: imageUrl,
46087
+ onChange: (e) => handleChange(fieldName2, e.target.value)
46088
+ }
45934
46089
  )
45935
46090
  ] });
45936
46091
  }