Skip to content

Commit 145d1fc

Browse files
authored
Merge pull request #684 from devtron-labs/feat/action-menu
feat: add support for action menu
2 parents 376f2e6 + 07c1423 commit 145d1fc

File tree

11 files changed

+169
-8
lines changed

11 files changed

+169
-8
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "1.10.15",
3+
"version": "1.10.16",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import PopupMenu from '@Common/PopupMenu'
2+
import { ActionMenuProps } from './types'
3+
import ActionMenuOption from './ActionMenuOption'
4+
import './actionMenu.scss'
5+
6+
const ActionMenu = ({ options, disableDescriptionEllipsis, children, onClick }: ActionMenuProps) => (
7+
<PopupMenu autoClose>
8+
<PopupMenu.Button isKebab rootClassName="flex left dc__no-background">
9+
{/* TODO: fix the issue with immediate button child */}
10+
{children}
11+
</PopupMenu.Button>
12+
<PopupMenu.Body rootClassName="dc__border mxh-300 dc__mnw-100 dc__mxw-250 dc__hide-hscroll dc__overflow-auto mt-4 mb-4">
13+
<div className="py-4">
14+
{options.length > 0
15+
? options.map((groupOrOption) =>
16+
'options' in groupOrOption ? (
17+
<div className="flexbox-col dc__gap-4 py-4 action-menu__group" key={groupOrOption.label}>
18+
<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">
19+
{groupOrOption.label}
20+
</h4>
21+
{/* Added this to contain the options in a container and have gap only b/w heading & container */}
22+
<div>
23+
{groupOrOption.options.length > 0 ? (
24+
groupOrOption.options.map((option) => (
25+
<ActionMenuOption
26+
key={option.value}
27+
option={option}
28+
onClick={onClick}
29+
disableDescriptionEllipsis={disableDescriptionEllipsis}
30+
/>
31+
))
32+
) : (
33+
<p className="fs-13 lh-18 fw-4 lh-18 cn-7 py-6 px-12 m-0">
34+
No options in group
35+
</p>
36+
)}
37+
</div>
38+
</div>
39+
) : (
40+
<ActionMenuOption
41+
key={groupOrOption.value}
42+
option={groupOrOption}
43+
onClick={onClick}
44+
disableDescriptionEllipsis={disableDescriptionEllipsis}
45+
/>
46+
),
47+
)
48+
: 'No Options'}
49+
</div>
50+
</PopupMenu.Body>
51+
</PopupMenu>
52+
)
53+
54+
export default ActionMenu
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Tooltip } from '@Common/Tooltip'
2+
import { ActionMenuOptionProps } from './types'
3+
import { getTooltipProps } from '../SelectPicker/common'
4+
5+
const ActionMenuOption = ({ option, onClick, disableDescriptionEllipsis }: ActionMenuOptionProps) => {
6+
const iconBaseClass = 'dc__no-shrink icon-dim-16 flex dc__fill-available-space'
7+
const { description, label, startIcon, endIcon, tooltipProps, type = 'neutral', isDisabled } = option
8+
const isNegativeType = type === 'negative'
9+
10+
const handleClick = () => {
11+
onClick(option)
12+
}
13+
14+
return (
15+
<Tooltip {...getTooltipProps(tooltipProps)}>
16+
<div
17+
// Intentionally added margin to the left and right to have the gap on the edges of the options
18+
className={`flex left dc__gap-8 ${description ? 'top' : ''} py-6 px-8 ${isDisabled ? 'dc__disabled' : 'cursor'} ${isNegativeType ? 'dc__hover-r50' : 'dc__hover-n50'} mr-4 ml-4 br-4 action-menu__option`}
19+
onClick={!isDisabled ? handleClick : undefined}
20+
aria-disabled={isDisabled}
21+
>
22+
{startIcon && <div className={`${iconBaseClass} mt-2`}>{startIcon}</div>}
23+
<div className="flex-grow-1">
24+
<Tooltip content={label} placement="right">
25+
<h4 className={`m-0 fs-13 fw-4 lh-20 dc__truncate ${isNegativeType ? 'cr-5' : 'cn-9'}`}>
26+
{label}
27+
</h4>
28+
</Tooltip>
29+
{description &&
30+
(typeof description === 'string' ? (
31+
<p
32+
className={`m-0 fs-12 fw-4 lh-18 cn-7 ${!disableDescriptionEllipsis ? 'dc__ellipsis-right__2nd-line' : 'dc__word-break'}`}
33+
>
34+
{description}
35+
</p>
36+
) : (
37+
<div className="fs-12 lh-18 cn-7">{description}</div>
38+
))}
39+
</div>
40+
{endIcon && <div className={iconBaseClass}>{endIcon}</div>}
41+
</div>
42+
</Tooltip>
43+
)
44+
}
45+
46+
export default ActionMenuOption
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.action-menu {
2+
&__group {
3+
border-top: 1px solid var(--border-secondary-translucent);
4+
5+
&:first-child {
6+
border-top: none;
7+
padding-top: 0;
8+
}
9+
10+
&:last-child {
11+
padding-bottom: 0;
12+
}
13+
}
14+
15+
&__option {
16+
& + .action-menu__group {
17+
margin-top: 4px;
18+
}
19+
}
20+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as ActionMenu } from './ActionMenu.component'
2+
export type { ActionMenuProps } from './types'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ReactElement } from 'react'
2+
import { GroupBase, OptionsOrGroups } from 'react-select'
3+
import { SelectPickerOptionType, SelectPickerProps } from '../SelectPicker'
4+
5+
type ActionMenuOptionType = SelectPickerOptionType & {
6+
isDisabled?: boolean
7+
/**
8+
* @default 'neutral'
9+
*/
10+
type?: 'neutral' | 'negative'
11+
}
12+
13+
export interface ActionMenuProps extends Pick<SelectPickerProps, 'disableDescriptionEllipsis'> {
14+
children: ReactElement
15+
options: OptionsOrGroups<ActionMenuOptionType, GroupBase<ActionMenuOptionType>>
16+
onClick: (option: SelectPickerOptionType) => void
17+
}
18+
19+
export interface ActionMenuOptionProps extends Pick<ActionMenuProps, 'onClick' | 'disableDescriptionEllipsis'> {
20+
option: ActionMenuOptionType
21+
}

src/Shared/Components/SelectPicker/SelectPicker.component.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,12 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
426426
...(isMulti
427427
? {
428428
option: () => 'checkbox__parent-container',
429+
...(isGroupHeadingSelectable
430+
? { groupHeading: () => 'checkbox__parent-container' }
431+
: {}),
429432
}
430433
: {}),
434+
group: () => 'select-picker__group',
431435
}}
432436
name={name || inputId}
433437
classNamePrefix={classNamePrefix || inputId}

src/Shared/Components/SelectPicker/common.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { getGroupCheckboxValue } from './utils'
4545
import { Icon } from '../Icon'
4646
import { Button, ButtonProps, ButtonVariantType } from '../Button'
4747

48-
const getTooltipProps = (tooltipProps: SelectPickerOptionType['tooltipProps'] = {}): TooltipProps => {
48+
export const getTooltipProps = (tooltipProps: SelectPickerOptionType['tooltipProps'] = {}): TooltipProps => {
4949
if (tooltipProps) {
5050
if (Object.hasOwn(tooltipProps, 'shortcutKeyCombo') && 'shortcutKeyCombo' in tooltipProps) {
5151
return tooltipProps
@@ -55,6 +55,7 @@ const getTooltipProps = (tooltipProps: SelectPickerOptionType['tooltipProps'] =
5555
// TODO: using some typing somersaults here, clean it up later
5656
alwaysShowTippyOnHover: !!(tooltipProps as Required<Pick<TooltipProps, 'content'>>)?.content,
5757
...(tooltipProps as Required<Pick<TooltipProps, 'content'>>),
58+
placement: 'right',
5859
}
5960
}
6061

src/Shared/Components/SelectPicker/utils.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ export const getCommonSelectStyle = <OptionValue, IsMulti extends boolean>({
187187
fontSize: '13px',
188188
lineHeight: '20px',
189189
fontWeight: 400,
190+
borderRadius: '4px',
191+
marginInline: '4px',
192+
width: 'auto',
190193

191194
':active': {
192195
backgroundColor: 'var(--N100)',
@@ -200,6 +203,10 @@ export const getCommonSelectStyle = <OptionValue, IsMulti extends boolean>({
200203
cursor: 'not-allowed',
201204
opacity: 0.5,
202205
}),
206+
207+
'& + .select-picker__group': {
208+
marginTop: '4px',
209+
},
203210
}),
204211
dropdownIndicator: (base, state) => ({
205212
...base,
@@ -288,11 +295,16 @@ export const getCommonSelectStyle = <OptionValue, IsMulti extends boolean>({
288295
}),
289296
group: (base) => ({
290297
...base,
291-
paddingTop: '4px',
292-
paddingBottom: 0,
298+
paddingBlock: '4px',
299+
borderTop: '1px solid var(--border-secondary-translucent)',
293300

294301
'&:first-child': {
295302
paddingTop: 0,
303+
borderTop: 'none',
304+
},
305+
306+
'&:last-child': {
307+
paddingBottom: 0,
296308
},
297309
}),
298310
groupHeading: (base) => ({
@@ -301,8 +313,8 @@ export const getCommonSelectStyle = <OptionValue, IsMulti extends boolean>({
301313
fontSize: '12px',
302314
color: 'var(--N900)',
303315
backgroundColor: 'var(--bg-menu-secondary)',
304-
marginBottom: 0,
305-
padding: '4px 8px',
316+
marginBottom: '4px',
317+
padding: '4px 12px',
306318
textTransform: 'none',
307319
overflow: 'hidden',
308320
textOverflow: 'ellipsis',

0 commit comments

Comments
 (0)