-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Popover & ActionMenu Component, Help Button, Create App Button Revamp #730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 15e2665
feat: ActionMenu - enhance component with search functionality and op…
RohitRaj011 94d1425
feat: ActionMenu - add overlay, improve trigger accessibility
RohitRaj011 965db8b
feat: revamp PageHeader Help button
RohitRaj011 9f74aad
feat: implement Popover component & usePopover hook
RohitRaj011 fc1f409
feat: ActionMenu - integrate Popover with usePopover hook
RohitRaj011 2b6940d
feat: ActionMenu - add default menu item icon color
RohitRaj011 65171bc
feat: ActionMenu - add support for footerConfig, code optimization
RohitRaj011 64e5186
feat: HelpButton - add CheckForUpdates option for OSS
RohitRaj011 d06ffdb
refactor: Popover - move zIndex from class to scss variable
RohitRaj011 4121e14
fix: update codemirror chunk
RohitRaj011 6f41bb6
fix: ActionMenu - add footer background color
RohitRaj011 4b0abc5
feat: ActionMenu - improve search box rendering and clean up styles
RohitRaj011 4597a75
refactor: code optimization
RohitRaj011 69a01bb
refactor: ActionMenu - add generic typing for id, code optimizations
RohitRaj011 2f75c95
refactor: HelpButton - update typings
RohitRaj011 10f2024
feat: HeaderWithCreateButton - update Create button with ActionMenu
RohitRaj011 2fba511
feat: Icon - add support for dataTestId, rotateBy and fillSpace props
RohitRaj011 ad794d6
refactor: HelpButton - optimize icon rotation handling, IconBase - p…
RohitRaj011 61d30a7
refactor: ActionMenu - update styling for searchbox and menu items, i…
RohitRaj011 2838a8b
Merge branch 'develop' of github.com:devtron-labs/devtron-fe-common-l…
RohitRaj011 485ebde
chore(version): bump to 1.13.0-pre-5
RohitRaj011 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
188
src/Shared/Components/ActionMenu/ActionMenu.component.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' : ''}` : ''}`} | ||
| onClick={!isDisabled ? handleClick : undefined} | ||
| aria-disabled={isDisabled} | ||
| > | ||
| {renderIcon(startIcon)} | ||
| {renderComponent()} | ||
| {renderIcon(endIcon)} | ||
| </li> | ||
| </Tooltip> | ||
| ) | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.