Skip to content

Commit 69a01bb

Browse files
committed
refactor: ActionMenu - add generic typing for id, code optimizations
1 parent 4597a75 commit 69a01bb

6 files changed

Lines changed: 47 additions & 38 deletions

File tree

src/Shared/Components/ActionMenu/ActionMenu.component.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useActionMenu } from './useActionMenu.hook'
99

1010
import './actionMenu.scss'
1111

12-
export const ActionMenu = ({
12+
export const ActionMenu = <T extends string | number = string | number>({
1313
id,
1414
options,
1515
onClick,
@@ -22,7 +22,7 @@ export const ActionMenu = ({
2222
children,
2323
onOpen,
2424
footerConfig,
25-
}: ActionMenuProps) => {
25+
}: ActionMenuProps<T>) => {
2626
// HOOKS
2727
const {
2828
open,
@@ -51,7 +51,7 @@ export const ActionMenu = ({
5151
// HANDLERS
5252
const handleOptionMouseEnter = (index: number) => () => setFocusedIndex(index)
5353

54-
const handleOptionOnClick = (item: ActionMenuItemType) => () => {
54+
const handleOptionOnClick = (item: ActionMenuItemType<T>) => () => {
5555
onClick(item)
5656
closePopover()
5757
}
@@ -107,7 +107,7 @@ export const ActionMenu = ({
107107
)
108108

109109
return (
110-
<ActionMenuItem
110+
<ActionMenuItem<T>
111111
key={`${item.label}-${item.id}`}
112112
item={item}
113113
itemRef={itemsRef.current[index]}

src/Shared/Components/ActionMenu/ActionMenuItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import { Icon } from '../Icon'
77
import { getTooltipProps } from '../SelectPicker/common'
88
import { ActionMenuItemProps } from './types'
99

10-
export const ActionMenuItem = ({
10+
export const ActionMenuItem = <T extends string | number>({
1111
item,
1212
itemRef,
1313
isFocused,
1414
onClick,
1515
onMouseEnter,
1616
disableDescriptionEllipsis = false,
17-
}: ActionMenuItemProps) => {
17+
}: ActionMenuItemProps<T>) => {
1818
const {
1919
id,
2020
description,

src/Shared/Components/ActionMenu/types.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ type ActionMenuItemIconType = Pick<IconsProps, 'name'> & {
3232
color?: IconsProps['color']
3333
}
3434

35-
export type ActionMenuItemType = Omit<SelectPickerOptionType, 'label' | 'value' | 'endIcon' | 'startIcon'> & {
35+
export type ActionMenuItemType<T extends string | number = string | number> = Omit<
36+
SelectPickerOptionType,
37+
'label' | 'value' | 'endIcon' | 'startIcon'
38+
> & {
3639
/** A unique identifier for the action menu item. */
37-
id: string | number
40+
id: T
3841
/** The text label for the menu item. */
3942
label: string
4043
/** Indicates whether the menu item is disabled. */
@@ -50,7 +53,7 @@ export type ActionMenuItemType = Omit<SelectPickerOptionType, 'label' | 'value'
5053
endIcon?: ActionMenuItemIconType
5154
} & ConditionalActionMenuComponentType
5255

53-
export type ActionMenuOptionType = {
56+
export type ActionMenuOptionType<T extends string | number> = {
5457
/**
5558
* The label for the group of menu items. \
5659
* This is optional and can be used to categorize items under a specific group.
@@ -59,27 +62,30 @@ export type ActionMenuOptionType = {
5962
/**
6063
* The list of items belonging to this group.
6164
*/
62-
items: ActionMenuItemType[]
65+
items: ActionMenuItemType<T>[]
6366
}
6467

65-
export type UseActionMenuProps = Omit<UsePopoverProps, 'onPopoverKeyDown' | 'onTriggerKeyDown'> & {
68+
export type UseActionMenuProps<T extends string | number> = Omit<
69+
UsePopoverProps,
70+
'onPopoverKeyDown' | 'onTriggerKeyDown'
71+
> & {
6672
/**
6773
* The options to display in the action menu.
6874
*/
69-
options: ActionMenuOptionType[]
75+
options: ActionMenuOptionType<T>[]
7076
/**
7177
* Determines whether the action menu is searchable.
7278
*/
7379
isSearchable?: boolean
7480
}
7581

76-
export type ActionMenuProps = UseActionMenuProps &
82+
export type ActionMenuProps<T extends string | number = string | number> = UseActionMenuProps<T> &
7783
Pick<SelectPickerProps, 'disableDescriptionEllipsis'> & {
7884
/**
7985
* Callback function triggered when an item is clicked.
8086
* @param item - The selected item.
8187
*/
82-
onClick: (item: ActionMenuItemType) => void
88+
onClick: (item: ActionMenuItemType<T>) => void
8389
/**
8490
* Config for the footer at the bottom of action menu list. It is sticky by default
8591
*/
@@ -102,8 +108,11 @@ export type ActionMenuProps = UseActionMenuProps &
102108
}
103109
)
104110

105-
export type ActionMenuItemProps = Pick<ActionMenuProps, 'onClick' | 'disableDescriptionEllipsis'> & {
106-
item: ActionMenuItemType
111+
export type ActionMenuItemProps<T extends string | number> = Pick<
112+
ActionMenuProps<T>,
113+
'onClick' | 'disableDescriptionEllipsis'
114+
> & {
115+
item: ActionMenuItemType<T>
107116
itemRef: Ref<HTMLAnchorElement> | LegacyRef<HTMLAnchorElement | HTMLButtonElement>
108117
isFocused?: boolean
109118
onMouseEnter?: () => void

src/Shared/Components/ActionMenu/useActionMenu.hook.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import { usePopover, UsePopoverProps } from '../Popover'
44
import { UseActionMenuProps } from './types'
55
import { filterActionMenuOptions, getActionMenuFlatOptions } from './utils'
66

7-
export const useActionMenu = ({
7+
export const useActionMenu = <T extends string | number>({
88
id,
99
position = 'bottom',
1010
alignment = 'start',
1111
width = 'auto',
1212
options,
1313
isSearchable,
1414
onOpen,
15-
}: UseActionMenuProps) => {
15+
}: UseActionMenuProps<T>) => {
1616
// STATES
1717
const [focusedIndex, setFocusedIndex] = useState(-1)
1818
const [searchTerm, setSearchTerm] = useState('')
@@ -102,9 +102,11 @@ export const useActionMenu = ({
102102
onTriggerKeyDown: handleTriggerKeyDown,
103103
})
104104

105+
// CLEANING UP STATES AFTER ACTION MENU CLOSE
105106
useEffect(() => {
106107
if (!open) {
107108
setFocusedIndex(-1)
109+
setSearchTerm('')
108110
}
109111
}, [open])
110112

src/Shared/Components/ActionMenu/utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { UseActionMenuProps } from './types'
22

3-
export const getActionMenuFlatOptions = (options: UseActionMenuProps['options']) =>
3+
export const getActionMenuFlatOptions = <T extends string | number>(options: UseActionMenuProps<T>['options']) =>
44
options.flatMap(
55
(option, sectionIndex) =>
66
option.items.map((groupOption, itemIndex) => ({
@@ -15,7 +15,10 @@ const normalize = (str: string) => str.toLowerCase()
1515

1616
const fuzzyMatch = (text: string, term: string) => normalize(text).includes(term)
1717

18-
export const filterActionMenuOptions = (options: UseActionMenuProps['options'], searchTerm: string) => {
18+
export const filterActionMenuOptions = <T extends string | number>(
19+
options: UseActionMenuProps<T>['options'],
20+
searchTerm: string,
21+
) => {
1922
if (!searchTerm) {
2023
return options
2124
}

src/Shared/Components/Popover/utils.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,32 +40,27 @@ export const getPopoverPositionStyle = ({ position }: Pick<UsePopoverProps, 'pos
4040
}
4141
}
4242

43+
const getPopoverAnimationProps = (axisKey: 'x' | 'y', axisInitialValue: number, isMiddleAlignment: boolean) =>
44+
({
45+
initial: { opacity: 0, [axisKey]: axisInitialValue },
46+
animate: { opacity: 1, [axisKey]: 0 },
47+
exit: { opacity: 0, [axisKey]: axisInitialValue },
48+
transformTemplate: (isMiddleAlignment
49+
? (params) =>
50+
axisKey === 'y' ? `translate(-50%, ${params[axisKey]})` : `translate(${params[axisKey]}, -50%,)`
51+
: undefined) as HTMLMotionProps<'div'>['transformTemplate'],
52+
}) satisfies HTMLMotionProps<'div'>
53+
4354
export const getPopoverFramerProps = ({ position, alignment }: Pick<UsePopoverProps, 'position' | 'alignment'>) => {
4455
const isMiddleAlignment = alignment === 'middle'
4556

4657
if (position === 'top' || position === 'bottom') {
4758
const initialY = position === 'bottom' ? -12 : 12
48-
49-
return {
50-
initial: { opacity: 0, y: initialY },
51-
animate: { opacity: 1, y: 0 },
52-
exit: { opacity: 0, y: initialY },
53-
transformTemplate: (isMiddleAlignment
54-
? ({ y }) => `translate(-50%, ${y})`
55-
: undefined) as HTMLMotionProps<'div'>['transformTemplate'],
56-
} satisfies HTMLMotionProps<'div'>
59+
return getPopoverAnimationProps('y', initialY, isMiddleAlignment)
5760
}
5861

5962
const initialX = position === 'right' ? -12 : 12
60-
61-
return {
62-
initial: { opacity: 0, x: initialX },
63-
animate: { opacity: 1, x: 0 },
64-
exit: { opacity: 0, x: initialX },
65-
transformTemplate: (isMiddleAlignment
66-
? ({ x }) => `translate(${x}, -50%)`
67-
: undefined) as HTMLMotionProps<'div'>['transformTemplate'],
68-
} satisfies HTMLMotionProps<'div'>
63+
return getPopoverAnimationProps('x', initialX, isMiddleAlignment)
6964
}
7065

7166
export const getPopoverActualPositionAlignment = ({

0 commit comments

Comments
 (0)