@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.
@@ -24519,6 +24519,7 @@ var init_DashboardLayout = __esm({
24519
24519
  onNotificationClick,
24520
24520
  showThemeToggle = true,
24521
24521
  sidebarFooter,
24522
+ sidebarContent,
24522
24523
  onSignOut: onSignOutProp,
24523
24524
  currentPath,
24524
24525
  layoutMode = "sidebar",
@@ -24644,7 +24645,11 @@ var init_DashboardLayout = __esm({
24644
24645
  {
24645
24646
  as: "nav",
24646
24647
  gap: "none",
24647
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
24648
+ className: cn(
24649
+ "py-4 space-y-1 overflow-y-auto",
24650
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
24651
+ isRail ? "px-2" : "px-3"
24652
+ ),
24648
24653
  children: navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
24649
24654
  NavLink,
24650
24655
  {
@@ -24656,6 +24661,7 @@ var init_DashboardLayout = __esm({
24656
24661
  ))
24657
24662
  }
24658
24663
  ),
24664
+ 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 }),
24659
24665
  sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
24660
24666
  ]
24661
24667
  }
@@ -27065,7 +27071,17 @@ function fileIcon(name) {
27065
27071
  return "file";
27066
27072
  }
27067
27073
  }
27068
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
27074
+ function ancestorsOf(id, byId) {
27075
+ const out = /* @__PURE__ */ new Set();
27076
+ if (!id) return out;
27077
+ let cur = byId.get(id)?.parentId;
27078
+ while (cur && byId.has(cur) && !out.has(cur)) {
27079
+ out.add(cur);
27080
+ cur = byId.get(cur)?.parentId;
27081
+ }
27082
+ return out;
27083
+ }
27084
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
27069
27085
  var init_FileTree = __esm({
27070
27086
  "components/core/molecules/FileTree.tsx"() {
27071
27087
  "use client";
@@ -27156,42 +27172,77 @@ var init_FileTree = __esm({
27156
27172
  indent,
27157
27173
  childrenByParent,
27158
27174
  onNodeSelect,
27159
- defaultExpanded = false
27175
+ onNodeAction,
27176
+ nodeActionIcon,
27177
+ nodeActionLabel,
27178
+ selectedId,
27179
+ look,
27180
+ isExpanded,
27181
+ onToggle
27160
27182
  }) => {
27161
- const [expanded, setExpanded] = React85.useState(defaultExpanded || depth < 1);
27162
27183
  const children = childrenByParent.get(item.id);
27163
27184
  const hasChildren = !!children && children.length > 0;
27185
+ const expanded = hasChildren && isExpanded(item.id, depth);
27186
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
27187
+ const nav = look === "nav";
27164
27188
  const handleClick = React85.useCallback(() => {
27165
- if (hasChildren) setExpanded((prev) => !prev);
27189
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
27166
27190
  onNodeSelect?.(item.id);
27167
- }, [hasChildren, item.id, onNodeSelect]);
27191
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
27192
+ const handleChevron = React85.useCallback((e) => {
27193
+ e.stopPropagation();
27194
+ onToggle(item.id, expanded);
27195
+ }, [item.id, expanded, onToggle]);
27196
+ const handleAction = React85.useCallback((e) => {
27197
+ e.stopPropagation();
27198
+ onNodeAction?.(item.id);
27199
+ }, [item.id, onNodeAction]);
27168
27200
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
27169
27201
  /* @__PURE__ */ jsxRuntime.jsxs(
27170
27202
  Box,
27171
27203
  {
27172
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27204
+ 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"}`,
27173
27205
  style: { paddingLeft: depth * indent + 8 },
27174
27206
  onClick: handleClick,
27175
27207
  role: "treeitem",
27208
+ "aria-selected": isSelected,
27176
27209
  "aria-expanded": hasChildren ? expanded : void 0,
27177
27210
  children: [
27178
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
27211
+ 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(
27179
27212
  Icon,
27180
27213
  {
27181
27214
  name: expanded ? "chevron-down" : "chevron-right",
27182
27215
  size: "xs",
27183
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27216
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
27184
27217
  }
27185
- ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27218
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27186
27219
  /* @__PURE__ */ jsxRuntime.jsx(
27187
27220
  Icon,
27188
27221
  {
27189
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27222
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27190
27223
  size: "xs",
27191
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27224
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
27225
+ }
27226
+ ),
27227
+ /* @__PURE__ */ jsxRuntime.jsx(
27228
+ Typography,
27229
+ {
27230
+ variant: "caption",
27231
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
27232
+ children: item.label
27192
27233
  }
27193
27234
  ),
27194
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27235
+ onNodeAction && /* @__PURE__ */ jsxRuntime.jsx(
27236
+ Box,
27237
+ {
27238
+ onClick: handleAction,
27239
+ role: "button",
27240
+ "aria-label": nodeActionLabel ?? "Node action",
27241
+ title: nodeActionLabel,
27242
+ 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"}`,
27243
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
27244
+ }
27245
+ )
27195
27246
  ]
27196
27247
  }
27197
27248
  ),
@@ -27202,47 +27253,116 @@ var init_FileTree = __esm({
27202
27253
  depth: depth + 1,
27203
27254
  indent,
27204
27255
  childrenByParent,
27205
- onNodeSelect
27256
+ onNodeSelect,
27257
+ onNodeAction,
27258
+ nodeActionIcon,
27259
+ nodeActionLabel,
27260
+ selectedId,
27261
+ look,
27262
+ isExpanded,
27263
+ onToggle
27206
27264
  },
27207
27265
  child.id
27208
27266
  )) })
27209
27267
  ] });
27210
27268
  };
27269
+ FlatFileTree = ({
27270
+ items,
27271
+ selectedId,
27272
+ look = "files",
27273
+ onNodeSelect,
27274
+ onNodeAction,
27275
+ nodeActionIcon,
27276
+ nodeActionLabel,
27277
+ className,
27278
+ indent = 16
27279
+ }) => {
27280
+ const [overrides, setOverrides] = React85.useState(() => /* @__PURE__ */ new Map());
27281
+ const byId = React85__namespace.default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
27282
+ const selectedAncestors = React85__namespace.default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
27283
+ const lastSelectedRef = React85__namespace.default.useRef(selectedId);
27284
+ React85__namespace.default.useEffect(() => {
27285
+ if (selectedId === lastSelectedRef.current) return;
27286
+ lastSelectedRef.current = selectedId;
27287
+ setOverrides((prev) => {
27288
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
27289
+ const next = new Map(prev);
27290
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
27291
+ return next;
27292
+ });
27293
+ }, [selectedId, selectedAncestors]);
27294
+ const isExpanded = React85.useCallback(
27295
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
27296
+ [overrides, selectedAncestors]
27297
+ );
27298
+ const onToggle = React85.useCallback((id, effective) => {
27299
+ setOverrides((prev) => {
27300
+ const next = new Map(prev);
27301
+ next.set(id, !effective);
27302
+ return next;
27303
+ });
27304
+ }, []);
27305
+ if (items.length === 0) return null;
27306
+ const ids = new Set(items.map((node) => node.id));
27307
+ const childrenByParent = /* @__PURE__ */ new Map();
27308
+ const roots = [];
27309
+ for (const item of items) {
27310
+ if (item.parentId && ids.has(item.parentId)) {
27311
+ const siblings = childrenByParent.get(item.parentId);
27312
+ if (siblings) siblings.push(item);
27313
+ else childrenByParent.set(item.parentId, [item]);
27314
+ } else {
27315
+ roots.push(item);
27316
+ }
27317
+ }
27318
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27319
+ FlatTreeNodeItem,
27320
+ {
27321
+ item,
27322
+ depth: 0,
27323
+ indent,
27324
+ childrenByParent,
27325
+ onNodeSelect,
27326
+ onNodeAction,
27327
+ nodeActionIcon,
27328
+ nodeActionLabel,
27329
+ selectedId,
27330
+ look,
27331
+ isExpanded,
27332
+ onToggle
27333
+ },
27334
+ item.id
27335
+ )) });
27336
+ };
27211
27337
  FileTree = ({
27212
27338
  tree,
27213
27339
  items,
27214
27340
  selectedPath,
27341
+ selectedId,
27342
+ look = "files",
27215
27343
  onFileSelect,
27216
27344
  onNodeSelect,
27345
+ onNodeAction,
27346
+ nodeActionIcon,
27347
+ nodeActionLabel,
27217
27348
  className,
27218
27349
  indent = 16
27219
27350
  }) => {
27220
27351
  if (items) {
27221
- if (items.length === 0) return null;
27222
- const ids = new Set(items.map((node) => node.id));
27223
- const childrenByParent = /* @__PURE__ */ new Map();
27224
- const roots = [];
27225
- for (const item of items) {
27226
- if (item.parentId && ids.has(item.parentId)) {
27227
- const siblings = childrenByParent.get(item.parentId);
27228
- if (siblings) siblings.push(item);
27229
- else childrenByParent.set(item.parentId, [item]);
27230
- } else {
27231
- roots.push(item);
27232
- }
27233
- }
27234
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27235
- FlatTreeNodeItem,
27352
+ return /* @__PURE__ */ jsxRuntime.jsx(
27353
+ FlatFileTree,
27236
27354
  {
27237
- item,
27238
- depth: 0,
27239
- indent,
27240
- childrenByParent,
27355
+ items,
27356
+ selectedId,
27357
+ look,
27241
27358
  onNodeSelect,
27242
- defaultExpanded: true
27243
- },
27244
- item.id
27245
- )) });
27359
+ onNodeAction,
27360
+ nodeActionIcon,
27361
+ nodeActionLabel,
27362
+ className,
27363
+ indent
27364
+ }
27365
+ );
27246
27366
  }
27247
27367
  if (!tree || tree.length === 0) return null;
27248
27368
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -36484,13 +36604,35 @@ var init_RichTextEditor = __esm({
36484
36604
  document.execCommand("createLink", false, safe);
36485
36605
  afterEdit();
36486
36606
  }, [afterEdit, t, toolbar.link]);
36607
+ const imageInputRef = React85.useRef(null);
36608
+ const savedRangeRef = React85.useRef(null);
36487
36609
  const execImage = React85.useCallback(() => {
36488
- const url = window.prompt(t("richTextEditor.imagePrompt"));
36489
- if (!url) return;
36490
- const safe = safeUrl(url, ["http://", "https://", "data:image/"]) ?? `https://${url}`;
36491
- document.execCommand("insertImage", false, safe);
36492
- afterEdit();
36493
- }, [afterEdit, t]);
36610
+ const root = ref.current;
36611
+ const sel = window.getSelection();
36612
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
36613
+ imageInputRef.current?.click();
36614
+ }, []);
36615
+ const handleImageFile = React85.useCallback((e) => {
36616
+ const file = e.target.files?.[0];
36617
+ e.target.value = "";
36618
+ if (!file) return;
36619
+ const reader = new FileReader();
36620
+ reader.onload = () => {
36621
+ const url = typeof reader.result === "string" ? reader.result : "";
36622
+ if (!url.startsWith("data:image/")) return;
36623
+ const root = ref.current;
36624
+ if (!root) return;
36625
+ root.focus();
36626
+ const sel = window.getSelection();
36627
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
36628
+ sel.removeAllRanges();
36629
+ sel.addRange(savedRangeRef.current);
36630
+ }
36631
+ document.execCommand("insertImage", false, url);
36632
+ afterEdit();
36633
+ };
36634
+ reader.readAsDataURL(file);
36635
+ }, [afterEdit]);
36494
36636
  const execRule = React85.useCallback(() => {
36495
36637
  document.execCommand("insertHorizontalRule", false);
36496
36638
  afterEdit();
@@ -36503,6 +36645,18 @@ var init_RichTextEditor = __esm({
36503
36645
  }
36504
36646
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
36505
36647
  /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
36648
+ /* @__PURE__ */ jsxRuntime.jsx(
36649
+ "input",
36650
+ {
36651
+ ref: imageInputRef,
36652
+ type: "file",
36653
+ accept: "image/*",
36654
+ className: "hidden",
36655
+ "aria-hidden": "true",
36656
+ tabIndex: -1,
36657
+ onChange: handleImageFile
36658
+ }
36659
+ ),
36506
36660
  showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
36507
36661
  Box,
36508
36662
  {
@@ -43402,15 +43556,6 @@ var init_Form = __esm({
43402
43556
  "data-testid": `image-preview-${fieldName2}`
43403
43557
  }
43404
43558
  ) : null,
43405
- /* @__PURE__ */ jsxRuntime.jsx(
43406
- Input,
43407
- {
43408
- ...commonProps,
43409
- type: "url",
43410
- value: imageUrl,
43411
- onChange: (e) => handleChange(fieldName2, e.target.value)
43412
- }
43413
- ),
43414
43559
  /* @__PURE__ */ jsxRuntime.jsx(
43415
43560
  UploadDropZone,
43416
43561
  {
@@ -43429,6 +43574,16 @@ var init_Form = __esm({
43429
43574
  reader.readAsDataURL(f3);
43430
43575
  }
43431
43576
  }
43577
+ ),
43578
+ /* @__PURE__ */ jsxRuntime.jsx(
43579
+ Input,
43580
+ {
43581
+ ...commonProps,
43582
+ type: "url",
43583
+ placeholder: t("form.imageUrlFallback"),
43584
+ value: imageUrl,
43585
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43586
+ }
43432
43587
  )
43433
43588
  ] });
43434
43589
  }