From 3ef6c4bdf15a1f0190ec28135c5b5aca6ef97c36 Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Mon, 15 Jun 2026 19:28:33 -0400 Subject: [PATCH 1/9] feat: adding SubmenuOffset interface --- src/elements/dropdown/types/nestedDropdown.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/elements/dropdown/types/nestedDropdown.ts b/src/elements/dropdown/types/nestedDropdown.ts index d6aada09..265453ea 100644 --- a/src/elements/dropdown/types/nestedDropdown.ts +++ b/src/elements/dropdown/types/nestedDropdown.ts @@ -17,6 +17,8 @@ export interface NestedDropdownConfig { enableKeyboardNavigation: boolean; /** Custom layer root ID prefix */ layerIdPrefix: string; + /** Offset for submenu positioning */ + offset?: SubmenuOffset; } /** @@ -28,7 +30,7 @@ export const DEFAULT_NESTED_CONFIG: NestedDropdownConfig = { maxDepth: 3, placement: 'right-start', enableKeyboardNavigation: true, - layerIdPrefix: 'nested-dropdown' + layerIdPrefix: 'nested-dropdown', }; /** @@ -97,4 +99,12 @@ export interface SubmenuPositioning { zIndex: number; /** Layer root ID */ layerId: string; + /** Offset for submenu positioning */ + offset?: SubmenuOffset; } + + +export interface SubmenuOffset { + left?: number; + top?: number; +} \ No newline at end of file From 7157751f82322e3d841e2cdedadc69c497d51b21 Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Mon, 15 Jun 2026 19:29:50 -0400 Subject: [PATCH 2/9] feat: using submenuOffset interface in order to be able to set offset for left or top --- .../dropdown/components/SubmenuTrigger.tsx | 15 ++++++++++++--- .../dropdown/hooks/useSubmenuPositioning.ts | 12 ++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/elements/dropdown/components/SubmenuTrigger.tsx b/src/elements/dropdown/components/SubmenuTrigger.tsx index f560a3d5..87c957cc 100644 --- a/src/elements/dropdown/components/SubmenuTrigger.tsx +++ b/src/elements/dropdown/components/SubmenuTrigger.tsx @@ -1,4 +1,5 @@ import {type Placement} from '@popperjs/core'; +import {type Options} from '@popperjs/core/lib/modifiers/offset'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {usePopper} from 'react-popper'; import {styled} from 'styled-components'; @@ -6,7 +7,7 @@ import {styled} from 'styled-components'; import {Layer} from '../../../components/layer/layer'; import {useNestedDropdown, useSubmenu} from '../context/NestedDropdownContext'; import {useSubmenuPositioning} from '../hooks/useSubmenuPositioning'; -import {SubmenuCapableProps} from '../types/nestedDropdown'; +import {SubmenuCapableProps, SubmenuOffset} from '../types/nestedDropdown'; interface SubmenuTriggerProps extends SubmenuCapableProps { children: React.ReactNode; @@ -115,7 +116,8 @@ export const SubmenuTrigger: React.FC = ({ submenuId: effectiveSubmenuId, level, placement: effectiveConfig.placement, - layerIdPrefix: effectiveConfig.layerIdPrefix + layerIdPrefix: effectiveConfig.layerIdPrefix, + offset: effectiveConfig.offset }); const timeoutRef = useRef(null); @@ -244,6 +246,7 @@ interface SubmenuPortalProps { placement: Placement; zIndex: number; layerId: string; + offset?: SubmenuOffset; }; onMouseEnter: () => void; onMouseLeave: () => void; @@ -254,6 +257,12 @@ interface SubmenuPortalProps { maxWidth: string; } +const DEFAULT_LEFT_OFFSET = 0; +const DEFAULT_TOP_OFFSET = 8; +const toPopperOffset = (incomingOffset?: SubmenuOffset): Options['offset'] => { + return [incomingOffset?.left ?? DEFAULT_LEFT_OFFSET, incomingOffset?.top ?? DEFAULT_TOP_OFFSET]; +} + /** * SubmenuPortal renders the submenu in a portal with custom Popper.js positioning. * @@ -295,7 +304,7 @@ const SubmenuPortal: React.FC = ({ { name: 'offset', options: { - offset: [0, 8] // 8px gap between trigger and submenu + offset: toPopperOffset(positioning.offset) // 8px gap between trigger and submenu } }, { diff --git a/src/elements/dropdown/hooks/useSubmenuPositioning.ts b/src/elements/dropdown/hooks/useSubmenuPositioning.ts index 50d70031..2bd2e0cb 100644 --- a/src/elements/dropdown/hooks/useSubmenuPositioning.ts +++ b/src/elements/dropdown/hooks/useSubmenuPositioning.ts @@ -1,7 +1,7 @@ import {Placement} from '@popperjs/core'; import {useCallback, useMemo} from 'react'; -import {SubmenuPositioning} from '../types/nestedDropdown'; +import {SubmenuPositioning, SubmenuOffset} from '../types/nestedDropdown'; /** * Hook for calculating submenu positioning, z-index, and layer management. @@ -27,6 +27,8 @@ interface UseSubmenuPositioningProps { placement?: Placement; /** Prefix for generating unique layer IDs */ layerIdPrefix?: string; + /** Offset for submenu positioning */ + offset?: SubmenuOffset; } /** @@ -86,7 +88,8 @@ export const useSubmenuPositioning = ({ submenuId, level, placement = 'right-start', - layerIdPrefix = 'nested-dropdown' + layerIdPrefix = 'nested-dropdown', + offset, }: UseSubmenuPositioningProps): UseSubmenuPositioningReturn => { /** * Calculates z-index based on nesting level to ensure proper stacking order. @@ -115,9 +118,10 @@ export const useSubmenuPositioning = ({ anchorElement, placement, zIndex: calculateZIndex(), - layerId: getLayerId() + layerId: getLayerId(), + offset }), - [placement, calculateZIndex, getLayerId] + [placement, calculateZIndex, getLayerId, offset] ); return useMemo( From 8c8059601dd54e9391eb07493b608c7491d9f85d Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Tue, 16 Jun 2026 10:52:34 -0400 Subject: [PATCH 3/9] chore: use direct return for toPopperOffset helper --- src/elements/dropdown/components/SubmenuTrigger.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/elements/dropdown/components/SubmenuTrigger.tsx b/src/elements/dropdown/components/SubmenuTrigger.tsx index 87c957cc..f41d6f1c 100644 --- a/src/elements/dropdown/components/SubmenuTrigger.tsx +++ b/src/elements/dropdown/components/SubmenuTrigger.tsx @@ -259,9 +259,7 @@ interface SubmenuPortalProps { const DEFAULT_LEFT_OFFSET = 0; const DEFAULT_TOP_OFFSET = 8; -const toPopperOffset = (incomingOffset?: SubmenuOffset): Options['offset'] => { - return [incomingOffset?.left ?? DEFAULT_LEFT_OFFSET, incomingOffset?.top ?? DEFAULT_TOP_OFFSET]; -} +const toPopperOffset = (incomingOffset?: SubmenuOffset): Options['offset'] => [incomingOffset?.left ?? DEFAULT_LEFT_OFFSET, incomingOffset?.top ?? DEFAULT_TOP_OFFSET] /** * SubmenuPortal renders the submenu in a portal with custom Popper.js positioning. From d24b5eb6462235a857a9e22a9f6cf29fe93c3fdf Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Tue, 16 Jun 2026 10:56:30 -0400 Subject: [PATCH 4/9] chore(lint): fix auto sort import --- src/elements/dropdown/hooks/useSubmenuPositioning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/dropdown/hooks/useSubmenuPositioning.ts b/src/elements/dropdown/hooks/useSubmenuPositioning.ts index 2bd2e0cb..2763a0da 100644 --- a/src/elements/dropdown/hooks/useSubmenuPositioning.ts +++ b/src/elements/dropdown/hooks/useSubmenuPositioning.ts @@ -1,7 +1,7 @@ import {Placement} from '@popperjs/core'; import {useCallback, useMemo} from 'react'; -import {SubmenuPositioning, SubmenuOffset} from '../types/nestedDropdown'; +import {SubmenuOffset, SubmenuPositioning} from '../types/nestedDropdown'; /** * Hook for calculating submenu positioning, z-index, and layer management. From 98e21405e1e878f9ba23526cc3a2325d07958158 Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Tue, 16 Jun 2026 12:55:33 -0400 Subject: [PATCH 5/9] feat: adding skidding and distance offset props --- src/elements/dropdown/components/SubmenuTrigger.tsx | 6 +++--- src/elements/dropdown/types/nestedDropdown.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/elements/dropdown/components/SubmenuTrigger.tsx b/src/elements/dropdown/components/SubmenuTrigger.tsx index f41d6f1c..1ee651c5 100644 --- a/src/elements/dropdown/components/SubmenuTrigger.tsx +++ b/src/elements/dropdown/components/SubmenuTrigger.tsx @@ -257,9 +257,9 @@ interface SubmenuPortalProps { maxWidth: string; } -const DEFAULT_LEFT_OFFSET = 0; -const DEFAULT_TOP_OFFSET = 8; -const toPopperOffset = (incomingOffset?: SubmenuOffset): Options['offset'] => [incomingOffset?.left ?? DEFAULT_LEFT_OFFSET, incomingOffset?.top ?? DEFAULT_TOP_OFFSET] +const DEFAULT_SKIDDING_OFFSET = 0; +const DEFAULT_DISTANCE_OFFSET = 8; +const toPopperOffset = (incomingOffset?: SubmenuOffset): Options['offset'] => [incomingOffset?.skidding ?? DEFAULT_SKIDDING_OFFSET, incomingOffset?.distance ?? DEFAULT_DISTANCE_OFFSET] /** * SubmenuPortal renders the submenu in a portal with custom Popper.js positioning. diff --git a/src/elements/dropdown/types/nestedDropdown.ts b/src/elements/dropdown/types/nestedDropdown.ts index 265453ea..64433df9 100644 --- a/src/elements/dropdown/types/nestedDropdown.ts +++ b/src/elements/dropdown/types/nestedDropdown.ts @@ -105,6 +105,6 @@ export interface SubmenuPositioning { export interface SubmenuOffset { - left?: number; - top?: number; + skidding?: number; + distance?: number; } \ No newline at end of file From 58265ad018f02d63f45e3afd31f6111dde1fed55 Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Tue, 16 Jun 2026 12:56:14 -0400 Subject: [PATCH 6/9] docs(storybook): adding storybook args for storybook component --- .../stories/nestedDropdown.stories.tsx | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx b/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx index 31d078ce..253846fc 100644 --- a/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx +++ b/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx @@ -2,9 +2,11 @@ import type {StoryFn} from '@storybook/react'; import React, {useState} from 'react'; import styled from 'styled-components'; +import {type Placement, placements} from '@popperjs/core'; import {palette} from '../../../../helpers/colorHelpers'; import {EmptyState} from '../../../emptyState/emptyState'; import {NestedDropdownProvider} from '../../context/NestedDropdownContext'; +import {NestedDropdownConfig} from '../../types/nestedDropdown'; import {Dropdown} from '../../dropdown'; import {DropdownButton} from '../../dropdownButton'; import {DropdownCoordinator} from '../../dropdownCoordinator'; @@ -123,7 +125,11 @@ const departmentsData = [ } ]; -const Template: StoryFn = () => { +type NestedDropdownStoryArgs = { + placement: Placement; +} + +const Template: StoryFn = () => { const [selectedValue, setSelectedValue] = useState(''); const [submenuSearchValues, setSubmenuSearchValues] = useState>({}); const [filteredSubmenuItems, setFilteredSubmenuItems] = useState>({}); @@ -229,7 +235,7 @@ CustomConfigDropdown.decorators = [ ]; // Multi-select nested dropdown example -const MultiSelectTemplate: StoryFn = () => { +const MultiSelectTemplate: StoryFn = ({ placement, offset }) => { const [selectedItems, setSelectedItems] = useState([]); const [submenuSearchValues, setSubmenuSearchValues] = useState>({}); @@ -256,9 +262,9 @@ const MultiSelectTemplate: StoryFn = () => { { ))} - }> + } + submenuConfig={{ offset }} + > { export const MultiSelectDropdown = MultiSelectTemplate.bind({}); MultiSelectDropdown.storyName = 'Multi-Select with Submenus'; +MultiSelectDropdown.args = { + placement: 'right-start', + offset: { + skidding: 0, + distance: 8 + } +}; + +MultiSelectDropdown.argTypes = { + placement: { + control: 'select', + options: placements + }, + offset: { + control: 'object', + description: 'Offset for the submenu positioning', + } +}; // Left-aligned submenu example const LeftAlignedTemplate: StoryFn = () => ( From 64ab5ae73fd8633ba95dce035ced90b77d6289c5 Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Tue, 16 Jun 2026 13:07:57 -0400 Subject: [PATCH 7/9] chore(prettier): some prettier fixes --- src/elements/dropdown/components/SubmenuTrigger.tsx | 5 ++++- src/elements/dropdown/hooks/useSubmenuPositioning.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/elements/dropdown/components/SubmenuTrigger.tsx b/src/elements/dropdown/components/SubmenuTrigger.tsx index 1ee651c5..c3271b16 100644 --- a/src/elements/dropdown/components/SubmenuTrigger.tsx +++ b/src/elements/dropdown/components/SubmenuTrigger.tsx @@ -259,7 +259,10 @@ interface SubmenuPortalProps { const DEFAULT_SKIDDING_OFFSET = 0; const DEFAULT_DISTANCE_OFFSET = 8; -const toPopperOffset = (incomingOffset?: SubmenuOffset): Options['offset'] => [incomingOffset?.skidding ?? DEFAULT_SKIDDING_OFFSET, incomingOffset?.distance ?? DEFAULT_DISTANCE_OFFSET] +const toPopperOffset = (incomingOffset?: SubmenuOffset): Options['offset'] => [ + incomingOffset?.skidding ?? DEFAULT_SKIDDING_OFFSET, + incomingOffset?.distance ?? DEFAULT_DISTANCE_OFFSET +]; /** * SubmenuPortal renders the submenu in a portal with custom Popper.js positioning. diff --git a/src/elements/dropdown/hooks/useSubmenuPositioning.ts b/src/elements/dropdown/hooks/useSubmenuPositioning.ts index 2763a0da..8ecfaab8 100644 --- a/src/elements/dropdown/hooks/useSubmenuPositioning.ts +++ b/src/elements/dropdown/hooks/useSubmenuPositioning.ts @@ -89,7 +89,7 @@ export const useSubmenuPositioning = ({ level, placement = 'right-start', layerIdPrefix = 'nested-dropdown', - offset, + offset }: UseSubmenuPositioningProps): UseSubmenuPositioningReturn => { /** * Calculates z-index based on nesting level to ensure proper stacking order. From 27fc7253ad56798778905013a86272c79929a7d2 Mon Sep 17 00:00:00 2001 From: Juorder Gonzalez Date: Tue, 16 Jun 2026 13:08:51 -0400 Subject: [PATCH 8/9] fix(lint): fix lint errors --- .../__docs__/stories/nestedDropdown.stories.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx b/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx index 253846fc..d8f194ca 100644 --- a/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx +++ b/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx @@ -1,12 +1,11 @@ +import {type Placement,placements} from '@popperjs/core'; import type {StoryFn} from '@storybook/react'; import React, {useState} from 'react'; import styled from 'styled-components'; -import {type Placement, placements} from '@popperjs/core'; import {palette} from '../../../../helpers/colorHelpers'; import {EmptyState} from '../../../emptyState/emptyState'; import {NestedDropdownProvider} from '../../context/NestedDropdownContext'; -import {NestedDropdownConfig} from '../../types/nestedDropdown'; import {Dropdown} from '../../dropdown'; import {DropdownButton} from '../../dropdownButton'; import {DropdownCoordinator} from '../../dropdownCoordinator'; @@ -127,7 +126,7 @@ const departmentsData = [ type NestedDropdownStoryArgs = { placement: Placement; -} +}; const Template: StoryFn = () => { const [selectedValue, setSelectedValue] = useState(''); @@ -235,7 +234,7 @@ CustomConfigDropdown.decorators = [ ]; // Multi-select nested dropdown example -const MultiSelectTemplate: StoryFn = ({ placement, offset }) => { +const MultiSelectTemplate: StoryFn = ({placement, offset}) => { const [selectedItems, setSelectedItems] = useState([]); const [submenuSearchValues, setSubmenuSearchValues] = useState>({}); @@ -264,7 +263,7 @@ const MultiSelectTemplate: StoryFn = ({ placement, offset }) => { openDelay: 200, closeDelay: 40000, maxDepth: 3, - placement, + placement }}> { ))} } - submenuConfig={{ offset }} - > + submenuConfig={{offset}}> Date: Tue, 16 Jun 2026 15:26:45 -0400 Subject: [PATCH 9/9] chore: restore closeDelay value to the original one --- .../dropdown/__docs__/stories/nestedDropdown.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx b/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx index d8f194ca..f81e570e 100644 --- a/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx +++ b/src/elements/dropdown/__docs__/stories/nestedDropdown.stories.tsx @@ -1,4 +1,4 @@ -import {type Placement,placements} from '@popperjs/core'; +import {type Placement, placements} from '@popperjs/core'; import type {StoryFn} from '@storybook/react'; import React, {useState} from 'react'; import styled from 'styled-components'; @@ -261,7 +261,7 @@ const MultiSelectTemplate: StoryFn = ({placement, offset}) => {