@almadar/ui 5.29.0 → 5.30.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.
@@ -21928,7 +21928,84 @@ var init_DashboardLayout = __esm({
21928
21928
  NavLinkBottom.displayName = "NavLinkBottom";
21929
21929
  }
21930
21930
  });
21931
- var Menu;
21931
+ function computeMenuStyle(position, triggerRect) {
21932
+ const isTop = position.startsWith("top");
21933
+ const isRight = position.endsWith("right") || position.endsWith("end");
21934
+ if (isTop) {
21935
+ return {
21936
+ top: triggerRect.top - MENU_GAP,
21937
+ transform: "translateY(-100%)",
21938
+ ...isRight ? { right: window.innerWidth - triggerRect.right } : { left: triggerRect.left }
21939
+ };
21940
+ }
21941
+ return {
21942
+ top: triggerRect.bottom + MENU_GAP,
21943
+ ...isRight ? { right: window.innerWidth - triggerRect.right } : { left: triggerRect.left }
21944
+ };
21945
+ }
21946
+ function SubMenu({
21947
+ items,
21948
+ itemRef,
21949
+ direction,
21950
+ eventBus
21951
+ }) {
21952
+ const [rect, setRect] = useState(null);
21953
+ useEffect(() => {
21954
+ if (itemRef) {
21955
+ setRect(itemRef.getBoundingClientRect());
21956
+ }
21957
+ }, [itemRef]);
21958
+ if (!rect) return null;
21959
+ const isRtl = direction === "rtl";
21960
+ const style = {
21961
+ top: rect.top,
21962
+ ...isRtl ? { right: window.innerWidth - rect.left } : { left: rect.right }
21963
+ };
21964
+ const panel = /* @__PURE__ */ jsx(
21965
+ "div",
21966
+ {
21967
+ className: cn("fixed z-50", menuContainerStyles),
21968
+ style,
21969
+ children: items.map((item, index) => {
21970
+ const isDivider = item.id === "divider" || item.label === "divider";
21971
+ const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
21972
+ const isDanger = item.variant === "danger";
21973
+ if (isDivider) {
21974
+ return /* @__PURE__ */ jsx(Divider, { className: "my-1" }, `divider-${index}`);
21975
+ }
21976
+ return /* @__PURE__ */ jsxs(
21977
+ Box,
21978
+ {
21979
+ as: "button",
21980
+ onClick: () => {
21981
+ if (item.disabled) return;
21982
+ if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
21983
+ item.onClick?.();
21984
+ },
21985
+ "aria-disabled": item.disabled || void 0,
21986
+ "data-testid": item.event ? `action-${item.event}` : void 0,
21987
+ className: cn(
21988
+ "w-full flex items-center gap-3 px-4 py-2 text-start",
21989
+ "text-sm transition-colors",
21990
+ "hover:bg-muted focus:outline-none focus:bg-muted",
21991
+ "disabled:opacity-50 disabled:cursor-not-allowed",
21992
+ item.disabled && "cursor-not-allowed",
21993
+ isDanger && "text-error hover:bg-error/10"
21994
+ ),
21995
+ children: [
21996
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
21997
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: cn("flex-1", isDanger && "text-red-600"), children: item.label }),
21998
+ item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs font-medium", children: item.badge })
21999
+ ]
22000
+ },
22001
+ itemId
22002
+ );
22003
+ })
22004
+ }
22005
+ );
22006
+ return typeof document !== "undefined" ? createPortal(panel, document.body) : panel;
22007
+ }
22008
+ var MENU_GAP, menuContainerStyles, Menu;
21932
22009
  var init_Menu = __esm({
21933
22010
  "components/core/molecules/Menu.tsx"() {
21934
22011
  "use client";
@@ -21939,6 +22016,14 @@ var init_Menu = __esm({
21939
22016
  init_Badge();
21940
22017
  init_cn();
21941
22018
  init_useEventBus();
22019
+ MENU_GAP = 4;
22020
+ menuContainerStyles = cn(
22021
+ "bg-card",
22022
+ "border-[length:var(--border-width)] border-border",
22023
+ "shadow-elevation-popover",
22024
+ "rounded-sm",
22025
+ "min-w-0 sm:min-w-[200px] max-w-[calc(100vw-1rem)] py-1"
22026
+ );
21942
22027
  Menu = ({
21943
22028
  trigger,
21944
22029
  items,
@@ -21946,9 +22031,10 @@ var init_Menu = __esm({
21946
22031
  className
21947
22032
  }) => {
21948
22033
  const eventBus = useEventBus();
21949
- const { t, direction } = useTranslate();
22034
+ const { direction } = useTranslate();
21950
22035
  const [isOpen, setIsOpen] = useState(false);
21951
22036
  const [activeSubMenu, setActiveSubMenu] = useState(null);
22037
+ const [activeSubMenuRef, setActiveSubMenuRef] = useState(null);
21952
22038
  const [triggerRect, setTriggerRect] = useState(null);
21953
22039
  const triggerRef = useRef(null);
21954
22040
  const menuRef = useRef(null);
@@ -21963,13 +22049,14 @@ var init_Menu = __esm({
21963
22049
  }
21964
22050
  setIsOpen(!isOpen);
21965
22051
  setActiveSubMenu(null);
22052
+ setActiveSubMenuRef(null);
21966
22053
  };
21967
- const handleItemClick = (item) => {
22054
+ const handleItemClick = (item, itemId) => {
21968
22055
  if (item.disabled) return;
21969
22056
  if (item.subMenu && item.subMenu.length > 0) {
21970
- setActiveSubMenu(item.id ?? null);
22057
+ setActiveSubMenu(itemId);
21971
22058
  } else {
21972
- if (item.event) eventBus.emit(`UI:${item.event}`, { itemId: item.id, label: item.label });
22059
+ if (item.event) eventBus.emit(`UI:${item.event}`, { itemId, label: item.label });
21973
22060
  item.onClick?.();
21974
22061
  setIsOpen(false);
21975
22062
  }
@@ -21984,22 +22071,12 @@ var init_Menu = __esm({
21984
22071
  if (isOpen && menuRef.current && !menuRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target)) {
21985
22072
  setIsOpen(false);
21986
22073
  setActiveSubMenu(null);
22074
+ setActiveSubMenuRef(null);
21987
22075
  }
21988
22076
  };
21989
22077
  document.addEventListener("mousedown", handleClickOutside);
21990
22078
  return () => document.removeEventListener("mousedown", handleClickOutside);
21991
22079
  }, [isOpen]);
21992
- const positionClasses = {
21993
- "top-left": "bottom-full left-0 mb-2",
21994
- "top-right": "bottom-full right-0 mb-2",
21995
- "bottom-left": "top-full left-0 mt-2",
21996
- "bottom-right": "top-full right-0 mt-2",
21997
- // Aliases for pattern compatibility
21998
- "top-start": "bottom-full left-0 mb-2",
21999
- "top-end": "bottom-full right-0 mb-2",
22000
- "bottom-start": "top-full left-0 mt-2",
22001
- "bottom-end": "top-full right-0 mt-2"
22002
- };
22003
22080
  const rtlMirror = {
22004
22081
  "top-left": "top-right",
22005
22082
  "top-right": "top-left",
@@ -22011,7 +22088,6 @@ var init_Menu = __esm({
22011
22088
  "bottom-end": "bottom-start"
22012
22089
  };
22013
22090
  const effectivePosition = direction === "rtl" ? rtlMirror[position] ?? position : position;
22014
- const subMenuSideClass = direction === "rtl" ? "right-full mr-2" : "left-full ml-2";
22015
22091
  const triggerChild = React80__default.isValidElement(trigger) ? trigger : /* @__PURE__ */ jsx(Typography, { variant: "small", as: "span", children: trigger });
22016
22092
  const triggerElement = React80__default.cloneElement(
22017
22093
  triggerChild,
@@ -22020,94 +22096,83 @@ var init_Menu = __esm({
22020
22096
  onClick: handleToggle
22021
22097
  }
22022
22098
  );
22023
- const menuContainerStyles = cn(
22024
- "bg-card",
22025
- "border-[length:var(--border-width)] border-border",
22026
- "shadow-elevation-popover",
22027
- "rounded-sm",
22028
- "min-w-0 sm:min-w-[200px] max-w-[calc(100vw-1rem)] py-1"
22029
- );
22030
- const renderMenuItem = (item, hasSubMenu, index) => {
22099
+ const renderMenuItems = (menuItems) => menuItems.map((item, index) => {
22100
+ const isDivider = item.id === "divider" || item.label === "divider";
22031
22101
  const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
22102
+ const hasSubMenu = !!(item.subMenu && item.subMenu.length > 0);
22032
22103
  const isDanger = item.variant === "danger";
22033
- return /* @__PURE__ */ jsx(
22034
- Box,
22035
- {
22036
- as: "button",
22037
- onClick: () => !item.disabled && handleItemClick({ ...item, id: itemId }),
22038
- "aria-disabled": item.disabled || void 0,
22039
- onMouseEnter: () => hasSubMenu && setActiveSubMenu(itemId),
22040
- "data-testid": item.event ? `action-${item.event}` : void 0,
22041
- className: cn(
22042
- "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
22043
- "text-sm transition-colors",
22044
- "hover:bg-muted",
22045
- "focus:outline-none focus:bg-muted",
22046
- "disabled:opacity-50 disabled:cursor-not-allowed",
22047
- item.disabled && "cursor-not-allowed",
22048
- isDanger && "text-error hover:bg-error/10"
22049
- ),
22050
- children: /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
22051
- item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
22052
- /* @__PURE__ */ jsx(
22053
- Typography,
22054
- {
22055
- variant: "small",
22056
- className: cn("flex-1", isDanger && "text-red-600"),
22057
- children: item.label
22104
+ if (isDivider) {
22105
+ return /* @__PURE__ */ jsx(Divider, { className: "my-1" }, `divider-${index}`);
22106
+ }
22107
+ return /* @__PURE__ */ jsxs(Box, { children: [
22108
+ /* @__PURE__ */ jsx(
22109
+ Box,
22110
+ {
22111
+ as: "button",
22112
+ onClick: () => handleItemClick({ ...item, id: itemId }, itemId),
22113
+ "aria-disabled": item.disabled || void 0,
22114
+ onMouseEnter: (e) => {
22115
+ if (hasSubMenu) {
22116
+ setActiveSubMenu(itemId);
22117
+ setActiveSubMenuRef(e.currentTarget);
22058
22118
  }
22119
+ },
22120
+ "data-testid": item.event ? `action-${item.event}` : void 0,
22121
+ className: cn(
22122
+ "w-full flex items-center justify-between gap-3 px-4 py-2 text-start",
22123
+ "text-sm transition-colors",
22124
+ "hover:bg-muted",
22125
+ "focus:outline-none focus:bg-muted",
22126
+ "disabled:opacity-50 disabled:cursor-not-allowed",
22127
+ item.disabled && "cursor-not-allowed",
22128
+ isDanger && "text-error hover:bg-error/10"
22059
22129
  ),
22060
- item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
22061
- hasSubMenu && /* @__PURE__ */ jsx(Icon, { name: direction === "rtl" ? "chevron-left" : "chevron-right", size: "sm", className: "flex-shrink-0" })
22062
- ] })
22063
- },
22064
- itemId
22065
- );
22066
- };
22067
- const renderMenuItems = (menuItems) => {
22068
- return menuItems.map((item, index) => {
22069
- const hasSubMenu = item.subMenu && item.subMenu.length > 0;
22070
- const isDivider = item.id === "divider" || item.label === "divider";
22071
- const itemId = item.id ?? `item-${item.label.toLowerCase().replace(/\s+/g, "-")}-${index}`;
22072
- if (isDivider) {
22073
- return /* @__PURE__ */ jsx(Divider, { className: "my-1" }, `divider-${index}`);
22074
- }
22075
- return /* @__PURE__ */ jsxs(Box, { children: [
22076
- renderMenuItem(item, !!hasSubMenu, index),
22077
- hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsx(
22078
- Box,
22079
- {
22080
- className: cn(
22081
- "absolute top-0 z-50",
22082
- subMenuSideClass,
22083
- menuContainerStyles
22130
+ children: /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-3 flex-1 min-w-0", children: [
22131
+ item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: "flex-shrink-0" }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: "flex-shrink-0" })),
22132
+ /* @__PURE__ */ jsx(
22133
+ Typography,
22134
+ {
22135
+ variant: "small",
22136
+ className: cn("flex-1", isDanger && "text-red-600"),
22137
+ children: item.label
22138
+ }
22084
22139
  ),
22085
- children: renderMenuItems(item.subMenu)
22086
- }
22087
- )
22088
- ] }, itemId);
22089
- });
22090
- };
22091
- return /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
22140
+ item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", children: item.badge }),
22141
+ hasSubMenu && /* @__PURE__ */ jsx(
22142
+ Icon,
22143
+ {
22144
+ name: direction === "rtl" ? "chevron-left" : "chevron-right",
22145
+ size: "sm",
22146
+ className: "flex-shrink-0"
22147
+ }
22148
+ )
22149
+ ] })
22150
+ }
22151
+ ),
22152
+ hasSubMenu && activeSubMenu === itemId && item.subMenu && /* @__PURE__ */ jsx(
22153
+ SubMenu,
22154
+ {
22155
+ items: item.subMenu,
22156
+ itemRef: activeSubMenuRef,
22157
+ direction,
22158
+ eventBus
22159
+ }
22160
+ )
22161
+ ] }, itemId);
22162
+ });
22163
+ const panel = isOpen && triggerRect ? /* @__PURE__ */ jsx(
22164
+ "div",
22165
+ {
22166
+ ref: menuRef,
22167
+ className: cn("fixed z-50", menuContainerStyles, className),
22168
+ style: computeMenuStyle(effectivePosition, triggerRect),
22169
+ role: "menu",
22170
+ children: renderMenuItems(items)
22171
+ }
22172
+ ) : null;
22173
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
22092
22174
  triggerElement,
22093
- isOpen && triggerRect && /* @__PURE__ */ jsx(
22094
- Box,
22095
- {
22096
- ref: menuRef,
22097
- className: cn(
22098
- "absolute z-50",
22099
- menuContainerStyles,
22100
- positionClasses[effectivePosition],
22101
- className
22102
- ),
22103
- style: {
22104
- left: effectivePosition.includes("left") ? 0 : "auto",
22105
- right: effectivePosition.includes("right") ? 0 : "auto"
22106
- },
22107
- role: "menu",
22108
- children: renderMenuItems(items)
22109
- }
22110
- )
22175
+ panel && typeof document !== "undefined" ? createPortal(panel, document.body) : panel
22111
22176
  ] });
22112
22177
  };
22113
22178
  Menu.displayName = "Menu";
@@ -36513,7 +36578,6 @@ var init_DocumentViewer = __esm({
36513
36578
  showPrint = false,
36514
36579
  actions,
36515
36580
  documents,
36516
- entity,
36517
36581
  isLoading = false,
36518
36582
  error,
36519
36583
  className
@@ -43309,7 +43373,7 @@ function TraitSlot({
43309
43373
  size = "md",
43310
43374
  showTooltip = true,
43311
43375
  categoryColors,
43312
- tooltipFrameUrl,
43376
+ tooltipFrameUrl = "",
43313
43377
  className,
43314
43378
  feedback,
43315
43379
  onItemDrop,
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * @packageDocumentation
11
11
  */
12
+ import type { PatternPropDef } from '@almadar/patterns';
12
13
  import type { PatternConfig, ResolvedPattern } from './types';
13
14
  /**
14
15
  * Component mapping entry from component-mapping.json
@@ -27,11 +28,7 @@ interface PatternDefinition {
27
28
  type: string;
28
29
  category: string;
29
30
  description: string;
30
- propsSchema?: Record<string, {
31
- required?: boolean;
32
- types?: string[];
33
- description?: string;
34
- }>;
31
+ propsSchema?: Record<string, PatternPropDef>;
35
32
  }
36
33
  /**
37
34
  * Initialize the pattern resolver with mappings.