Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
14b65fc
feat: ActionMenu - refactor component for improved functionality and …
RohitRaj011 May 8, 2025
15e2665
feat: ActionMenu - enhance component with search functionality and op…
RohitRaj011 May 8, 2025
94d1425
feat: ActionMenu - add overlay, improve trigger accessibility
RohitRaj011 May 9, 2025
965db8b
feat: revamp PageHeader Help button
RohitRaj011 May 9, 2025
9f74aad
feat: implement Popover component & usePopover hook
RohitRaj011 May 9, 2025
fc1f409
feat: ActionMenu - integrate Popover with usePopover hook
RohitRaj011 May 9, 2025
2b6940d
feat: ActionMenu - add default menu item icon color
RohitRaj011 May 12, 2025
65171bc
feat: ActionMenu - add support for footerConfig, code optimization
RohitRaj011 May 13, 2025
64e5186
feat: HelpButton - add CheckForUpdates option for OSS
RohitRaj011 May 13, 2025
d06ffdb
refactor: Popover - move zIndex from class to scss variable
RohitRaj011 May 13, 2025
4121e14
fix: update codemirror chunk
RohitRaj011 May 13, 2025
6f41bb6
fix: ActionMenu - add footer background color
RohitRaj011 May 13, 2025
4b0abc5
feat: ActionMenu - improve search box rendering and clean up styles
RohitRaj011 May 13, 2025
4597a75
refactor: code optimization
RohitRaj011 May 13, 2025
69a01bb
refactor: ActionMenu - add generic typing for id, code optimizations
RohitRaj011 May 14, 2025
2f75c95
refactor: HelpButton - update typings
RohitRaj011 May 14, 2025
10f2024
feat: HeaderWithCreateButton - update Create button with ActionMenu
RohitRaj011 May 14, 2025
2fba511
feat: Icon - add support for dataTestId, rotateBy and fillSpace props
RohitRaj011 May 14, 2025
ad794d6
refactor: HelpButton - optimize icon rotation handling, IconBase - p…
RohitRaj011 May 14, 2025
61d30a7
refactor: ActionMenu - update styling for searchbox and menu items, i…
RohitRaj011 May 14, 2025
2838a8b
Merge branch 'develop' of github.com:devtron-labs/devtron-fe-common-l…
RohitRaj011 May 16, 2025
485ebde
chore(version): bump to 1.13.0-pre-5
RohitRaj011 May 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtron-labs/devtron-fe-common-lib",
"version": "1.13.0-pre-4",
"version": "1.13.0-pre-5",
"description": "Supporting common component library",
"type": "module",
"main": "dist/index.js",
Expand Down
4 changes: 4 additions & 0 deletions src/Assets/IconV2/ic-chat-circle-online.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/IconV2/ic-discord-fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/IconV2/ic-edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/IconV2/ic-file-edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/IconV2/ic-file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/IconV2/ic-files.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/IconV2/ic-megaphone-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/Assets/IconV2/ic-path.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
188 changes: 137 additions & 51 deletions src/Shared/Components/ActionMenu/ActionMenu.component.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,142 @@
import PopupMenu from '@Common/PopupMenu'
import { MutableRefObject } from 'react'

import ActionMenuOption from './ActionMenuOption'
import { ActionMenuProps } from './types'
import { CustomInput } from '../CustomInput'
import { Popover } from '../Popover'
import { SelectPickerMenuListFooter } from '../SelectPicker/common'
import { ActionMenuItem } from './ActionMenuItem'
import { ActionMenuItemType, ActionMenuProps } from './types'
import { useActionMenu } from './useActionMenu.hook'

import './actionMenu.scss'

const ActionMenu = ({ options, disableDescriptionEllipsis, children, onClick }: ActionMenuProps) => (
<PopupMenu autoClose>
<PopupMenu.Button isKebab rootClassName="flex left dc__no-background">
{/* TODO: fix the issue with immediate button child */}
{children}
</PopupMenu.Button>
<PopupMenu.Body rootClassName="dc__border mxh-300 dc__mnw-100 dc__mxw-250 dc__hide-hscroll dc__overflow-auto mt-4 mb-4">
<div className="py-4">
{options.length > 0
? options.map((groupOrOption) =>
'options' in groupOrOption ? (
<div className="flexbox-col dc__gap-4 py-4 action-menu__group" key={groupOrOption.label}>
<h4 className="fs-12 lh-18 cn-9 fw-6 py-4 px-12 dc__truncate bg__menu--secondary m-0 dc__top-0 dc__zi-1 dc__position-sticky">
{groupOrOption.label}
</h4>
{/* Added this to contain the options in a container and have gap only b/w heading & container */}
<div>
{groupOrOption.options.length > 0 ? (
groupOrOption.options.map((option) => (
<ActionMenuOption
key={option.value}
option={option}
onClick={onClick}
disableDescriptionEllipsis={disableDescriptionEllipsis}
/>
))
) : (
<p className="fs-13 lh-18 fw-4 lh-18 cn-7 py-6 px-12 m-0">
No options in group
</p>
)}
</div>
</div>
) : (
<ActionMenuOption
key={groupOrOption.value}
option={groupOrOption}
onClick={onClick}
disableDescriptionEllipsis={disableDescriptionEllipsis}
/>
),
)
: 'No Options'}
</div>
</PopupMenu.Body>
</PopupMenu>
)
export const ActionMenu = <T extends string | number = string | number>({
id,
options,
onClick,
position,
alignment,
width,
isSearchable,
disableDescriptionEllipsis,
buttonProps,
children,
onOpen,
footerConfig,
}: ActionMenuProps<T>) => {
// HOOKS
const {
open,
filteredOptions,
flatOptions,
triggerProps,
overlayProps,
popoverProps,
focusedIndex,
searchTerm,
handleSearch,
itemsRef,
setFocusedIndex,
closePopover,
scrollableRef,
} = useActionMenu({
id,
options,
position,
alignment,
width,
isSearchable,
onOpen,
})

// HANDLERS
const handleOptionMouseEnter = (index: number) => () => setFocusedIndex(index)

const handleOptionOnClick = (item: ActionMenuItemType<T>) => () => {
onClick(item)
closePopover()
}

export default ActionMenu
return (
<Popover
open={open}
overlayProps={overlayProps}
popoverProps={popoverProps}
triggerProps={triggerProps}
buttonProps={buttonProps}
triggerElement={children}
>
<div className="flexbox-col mxh-300">
{isSearchable && (
<div
role="search"
className="action-menu__searchbox bg__primary border__secondary-translucent--bottom"
>
<CustomInput
name="action-menu-search-box"
value={searchTerm}
placeholder="Search"
onChange={handleSearch}
fullWidth
autoFocus
/>
</div>
)}
<ul
ref={scrollableRef as MutableRefObject<HTMLUListElement>}
role="menu"
className="action-menu m-0 p-0 flex-grow-1 dc__overflow-auto dc__overscroll-none"
>
{filteredOptions.length > 0 ? (
filteredOptions.map((option, sectionIndex) => (
<li
key={option.groupLabel || `no-group-label-${sectionIndex}`}
role="menuitem"
className="action-menu__group flexbox-col dc__gap-4 py-4"
>
{option.groupLabel && (
<h4 className="bg__menu--secondary dc__truncate m-0 fs-12 lh-18 cn-9 fw-6 py-4 px-12 dc__position-sticky dc__top-0 dc__zi-1">
{option.groupLabel}
</h4>
)}
{option.items.length > 0 ? (
<ul className="action-menu__group-list p-0">
{option.items.map((item, itemIndex) => {
const index = flatOptions.findIndex(
(flatOption) =>
flatOption.sectionIndex === sectionIndex &&
flatOption.itemIndex === itemIndex,
)

return (
<ActionMenuItem<T>
key={`${item.label}-${item.id}`}
item={item}
itemRef={itemsRef.current[index]}
isFocused={index === focusedIndex}
onMouseEnter={handleOptionMouseEnter(index)}
onClick={handleOptionOnClick(item)}
disableDescriptionEllipsis={disableDescriptionEllipsis}
/>
)
})}
</ul>
) : (
<p className="m-0 fs-13 lh-18 fw-4 cn-7 py-6 px-12">No options in this group</p>
)}
</li>
))
) : (
<li role="menuitem" className="py-8 px-12">
<p className="m-0 fs-13 lh-20 fw-4 cn-7">No options</p>
</li>
)}
</ul>
{footerConfig && (
<div className="bg__menu--secondary border__secondary-translucent--top">
<SelectPickerMenuListFooter menuListFooterConfig={footerConfig} />
</div>
)}
</div>
</Popover>
)
}
124 changes: 124 additions & 0 deletions src/Shared/Components/ActionMenu/ActionMenuItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { LegacyRef, Ref } from 'react'
import { Link } from 'react-router-dom'

import { Tooltip } from '@Common/Tooltip'

import { Icon } from '../Icon'
import { getTooltipProps } from '../SelectPicker/common'
import { ActionMenuItemProps } from './types'

export const ActionMenuItem = <T extends string | number>({
item,
itemRef,
isFocused,
onClick,
onMouseEnter,
disableDescriptionEllipsis = false,
}: ActionMenuItemProps<T>) => {
const {
id,
description,
label,
startIcon,
endIcon,
tooltipProps,
type = 'neutral',
isDisabled,
componentType = 'button',
} = item

// REFS
const ref: LegacyRef<HTMLLIElement> = (el) => {
if (isFocused && el) {
el.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
}
}

// CONSTANTS
const isNegativeType = type === 'negative'

// HANDLERS
const handleClick = () => {
onClick(item)
}

// RENDERERS
const renderIcon = (iconProps: typeof startIcon) =>
iconProps && (
<div className="mt-2 flex dc__no-shrink">
<Icon {...iconProps} color={iconProps.color || (isNegativeType ? 'R500' : 'N800')} />
</div>
)

const renderContent = () => (
<>
<Tooltip content={label} placement="right">
<span className={`m-0 fs-13 fw-4 lh-20 dc__truncate ${isNegativeType ? 'cr-5' : 'cn-9'}`}>{label}</span>
</Tooltip>
{description &&
(typeof description === 'string' ? (
<span
className={`m-0 fs-12 fw-4 lh-18 cn-7 ${!disableDescriptionEllipsis ? 'dc__ellipsis-right__2nd-line' : 'dc__word-break'}`}
>
{description}
</span>
) : (
description
))}
</>
)

const renderComponent = () => {
switch (componentType) {
case 'anchor':
return (
<a
ref={itemRef as LegacyRef<HTMLAnchorElement>}
className="flex-grow-1"
href={item.href}
target="_blank"
rel="noreferrer"
>
{renderContent()}
</a>
)
case 'link':
return (
<Link ref={itemRef as Ref<HTMLAnchorElement>} className="flex-grow-1" to={item.to}>
{renderContent()}
</Link>
)
case 'button':
default:
return (
<button
ref={itemRef as LegacyRef<HTMLButtonElement>}
type="button"
className="dc__transparent p-0 flex-grow-1"
>
{renderContent()}
</button>
)
}
}

return (
<Tooltip {...getTooltipProps(tooltipProps)}>
<li
ref={ref}
role="menuitem"
data-testid={`action-menu-item-${id}`}
onMouseEnter={onMouseEnter}
tabIndex={-1}
// Intentionally added margin to the left and right to have the gap on the edges of the options
className={`action-menu__option br-4 flex left top dc__gap-8 mr-4 ml-4 py-6 px-8 ${isDisabled ? 'dc__disabled' : 'cursor'} ${isNegativeType ? 'dc__hover-r50' : 'dc__hover-n50'} ${isFocused ? `action-menu__option--focused${isNegativeType ? '-negative' : ''}` : ''}`}
Comment thread
AbhishekA1509 marked this conversation as resolved.
onClick={!isDisabled ? handleClick : undefined}
aria-disabled={isDisabled}
>
{renderIcon(startIcon)}
{renderComponent()}
{renderIcon(endIcon)}
</li>
</Tooltip>
)
}
Loading