@aurora-ds/components 1.8.1 → 1.8.3

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.
package/dist/esm/index.js CHANGED
@@ -3466,6 +3466,13 @@ width = '100%', minWidth = 0, maxWidth, flex, flexGrow, flexShrink, flexBasis, .
3466
3466
  };
3467
3467
  TextField.displayName = 'TextField';
3468
3468
 
3469
+ /**
3470
+ * Vertical padding (in px) applied to the top and bottom of the menu panel.
3471
+ * Matches `theme.spacing.xs` (0.25rem = 4px at 16px base font).
3472
+ * Exported so submenus can offset their position upward by this amount to align
3473
+ * their first item with the trigger item.
3474
+ */
3475
+ const MENU_PANEL_PADDING_Y_PX = 5;
3469
3476
  const MENU_PANEL_STYLES = createStyles((theme) => ({
3470
3477
  backdrop: {
3471
3478
  position: 'fixed',
@@ -3539,7 +3546,7 @@ const MENU_MIN_WIDTH_PX = 224;
3539
3546
  * Uses a two-pass strategy: first render with `menuHeight = 0`, then a rAF
3540
3547
  * recompute with the actual rendered size to apply any overflow offset.
3541
3548
  */
3542
- const useMenuPosition = ({ anchorEl, open, menuRef, minWidth, gap = 4, placement = 'bottom', }) => {
3549
+ const useMenuPosition = ({ anchorEl, open, menuRef, minWidth, gap = 4, verticalOffset = 0, placement = 'bottom', }) => {
3543
3550
  const [style, setStyle] = useState({});
3544
3551
  // Stays false until the rAF second pass has run with the real panel dimensions.
3545
3552
  // Keeps the panel invisible during the initial style={} state and the first
@@ -3556,8 +3563,10 @@ const useMenuPosition = ({ anchorEl, open, menuRef, minWidth, gap = 4, placement
3556
3563
  const viewportRight = window.innerWidth - VIEWPORT_MARGIN_PX;
3557
3564
  const viewportBottom = window.innerHeight - VIEWPORT_MARGIN_PX;
3558
3565
  if (placement === 'right' || placement === 'left') {
3559
- // --- Vertical: align with the anchor top, clamp inside the viewport ---
3560
- let top = anchor.top;
3566
+ // --- Vertical: align with the anchor top, offset upward by verticalOffset
3567
+ // (e.g. the panel's paddingTop) so the first item lines up with the trigger,
3568
+ // then clamp inside the viewport. ---
3569
+ let top = anchor.top - verticalOffset;
3561
3570
  if (menuHeight > 0 && top + menuHeight > viewportBottom) {
3562
3571
  top = Math.max(VIEWPORT_MARGIN_PX, viewportBottom - menuHeight);
3563
3572
  }
@@ -3617,7 +3626,7 @@ const useMenuPosition = ({ anchorEl, open, menuRef, minWidth, gap = 4, placement
3617
3626
  const maxLeft = window.innerWidth - effectiveWidth - VIEWPORT_MARGIN_PX;
3618
3627
  const left = Math.max(VIEWPORT_MARGIN_PX, Math.min(anchor.left, maxLeft));
3619
3628
  setStyle({ top, left, minWidth: minWidth ?? fallbackMinWidth });
3620
- }, [anchorEl, menuRef, minWidth, gap, placement]);
3629
+ }, [anchorEl, menuRef, minWidth, gap, verticalOffset, placement]);
3621
3630
  // First pass: compute as soon as the menu opens (menuHeight = 0)
3622
3631
  useLayoutEffect(() => {
3623
3632
  if (open) {
@@ -3690,12 +3699,12 @@ const ITEM_SELECTOR = '[role^="menuitem"]:not([data-disabled]), [role="option"]:
3690
3699
  * `aria-activedescendant`. Item order is read from the DOM (read-only) so the
3691
3700
  * logic stays correct across groups and dynamic content.
3692
3701
  */
3693
- const useMenuPanel = ({ open, onClose, anchorEl, minWidth, placement = 'bottom', onArrowLeft, }) => {
3702
+ const useMenuPanel = ({ open, onClose, anchorEl, minWidth, placement = 'bottom', verticalOffset = 0, onArrowLeft, }) => {
3694
3703
  const panelRef = useRef(null);
3695
3704
  const [focusedId, setFocusedId] = useState(undefined);
3696
3705
  // True while a descendant submenu is open → pause this panel's keyboard nav.
3697
3706
  const [childOpen, setChildOpen] = useState(false);
3698
- const { style } = useMenuPosition({ anchorEl, open, menuRef: panelRef, minWidth, placement });
3707
+ const { style } = useMenuPosition({ anchorEl, open, menuRef: panelRef, minWidth, placement, verticalOffset });
3699
3708
  /** Read this panel's focusable items in DOM order (excluding nested submenus). */
3700
3709
  const getItems = useCallback(() => {
3701
3710
  const panel = panelRef.current;
@@ -3909,6 +3918,9 @@ const MenuPanel = ({ open, onClose, anchorEl, minWidth, maxHeight = '20rem', pla
3909
3918
  anchorEl,
3910
3919
  minWidth,
3911
3920
  placement,
3921
+ // Shift submenus upward by the panel's paddingTop so the first item aligns
3922
+ // visually with the trigger item in the parent menu.
3923
+ verticalOffset: isSubmenu ? MENU_PANEL_PADDING_Y_PX : 0,
3912
3924
  onArrowLeft,
3913
3925
  });
3914
3926
  const contextValue = useMemo(() => ({ setChildOpen, closeMenu }), [setChildOpen, closeMenu]);