Skip to content

Commit 1b8b56c

Browse files
authored
Merge pull request #303 from devtron-labs/feat/app-details-resource-tree-ui
feat: TabGroup - add xl size variant, add description support
2 parents 5697f74 + 9732dc3 commit 1b8b56c

File tree

10 files changed

+231
-109
lines changed

10 files changed

+231
-109
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": "0.3.6",
3+
"version": "0.3.7",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Common/Drawer/Drawer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const Drawer = ({
4141
maxWidth,
4242
parentClassName,
4343
onEscape,
44+
onClose,
4445
}: drawerInterface) => {
4546
const drawerRef = useRef(null)
4647
useEffect(() => {
@@ -61,7 +62,7 @@ export const Drawer = ({
6162
style['--height'] = height
6263
}
6364
return (
64-
<VisibleModal className="drawer--container" parentClassName={parentClassName || ''} onEscape={onEscape}>
65+
<VisibleModal className="drawer--container" parentClassName={parentClassName || ''} onEscape={onEscape} close={onClose}>
6566
<aside style={style} ref={drawerRef} className={`drawer ${position}`}>
6667
{children}
6768
</aside>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.gui-yaml-switch.status-filter-button {
2+
&.radio-group {
3+
.radio__item-label {
4+
padding: 2px 8px;
5+
display: flex;
6+
align-items: center;
7+
gap: 6px;
8+
}
9+
}
10+
11+
&.with-menu-button {
12+
border-top-right-radius: 0;
13+
border-bottom-right-radius: 0;
14+
15+
.radio:last-child > .radio__item-label {
16+
border-right: none;
17+
border-top-right-radius: 0 !important;
18+
border-bottom-right-radius: 0 !important;
19+
}
20+
}
21+
}

src/Shared/Components/CICDHistory/StatusFilterButtonComponent.tsx

Lines changed: 76 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,17 @@
1616

1717
/* eslint-disable eqeqeq */
1818
import { useEffect, useState } from 'react'
19-
import { StyledRadioGroup as RadioGroup } from '../../../Common'
19+
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
20+
import { PopupMenu, StyledRadioGroup as RadioGroup } from '../../../Common'
2021
import { NodeStatus, StatusFilterButtonType } from './types'
2122
import { IndexStore } from '../../Store'
2223

23-
interface TabState {
24-
status: string
25-
count: number
26-
isSelected: boolean
27-
}
24+
import './StatusFilterButtonComponent.scss'
2825

2926
export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: StatusFilterButtonType) => {
3027
const [selectedTab, setSelectedTab] = useState('all')
3128

29+
const maxInlineFilterCount = 4
3230
let allNodeCount: number = 0
3331
let healthyNodeCount: number = 0
3432
let progressingNodeCount: number = 0
@@ -50,7 +48,7 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
5048
allNodeCount += 1
5149
})
5250

53-
const filters = [
51+
const filterOptions = [
5452
{ status: 'all', count: allNodeCount, isSelected: selectedTab == 'all' },
5553
{ status: NodeStatus.Missing, count: missingNodeCount, isSelected: NodeStatus.Missing == selectedTab },
5654
{ status: NodeStatus.Degraded, count: failedNodeCount, isSelected: NodeStatus.Degraded == selectedTab },
@@ -61,6 +59,13 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
6159
},
6260
{ status: NodeStatus.Healthy, count: healthyNodeCount, isSelected: NodeStatus.Healthy == selectedTab },
6361
]
62+
const validFilterOptions = filterOptions.filter(({ count }) => count > 0)
63+
const displayedInlineFilters = validFilterOptions.slice(
64+
0,
65+
Math.min(maxInlineFilterCount, validFilterOptions.length),
66+
)
67+
const overflowFilters =
68+
validFilterOptions.length > maxInlineFilterCount ? validFilterOptions.slice(maxInlineFilterCount) : null
6469

6570
useEffect(() => {
6671
if (
@@ -81,30 +86,70 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
8186
setSelectedTab(event.target.value)
8287
}
8388

89+
const handleMenuOptionClick = (status: string) => () => setSelectedTab(status)
90+
91+
const renderOverflowFilters = () =>
92+
overflowFilters ? (
93+
<PopupMenu autoClose>
94+
<PopupMenu.Button
95+
isKebab
96+
rootClassName="flex p-4 dc__border dc__no-left-radius dc__right-radius-4 bcn-0 dc__hover-n50"
97+
>
98+
<ICCaretDown className="icon-dim-14 scn-6" />
99+
</PopupMenu.Button>
100+
<PopupMenu.Body rootClassName="w-150 py-4 mt-4" style={{ left: '136px' }}>
101+
{overflowFilters.map((filter) => (
102+
<button
103+
key={filter.status}
104+
type="button"
105+
className={`dc__transparent w-100 py-6 px-8 flex left dc__gap-8 fs-13 lh-20 fw-4 cn-9 ${filter.isSelected ? 'bcb-1' : 'bcn-0 dc__hover-n50'}`}
106+
onClick={handleMenuOptionClick(filter.status)}
107+
>
108+
<span
109+
className={`dc__app-summary__icon icon-dim-16 ${filter.status} ${filter.status}--node`}
110+
style={{ zIndex: 'unset' }}
111+
/>
112+
<span className="dc__first-letter-capitalize flex-grow-1 text-left">{filter.status}</span>
113+
<span>{filter.count}</span>
114+
</button>
115+
))}
116+
</PopupMenu.Body>
117+
</PopupMenu>
118+
) : null
119+
84120
return (
85-
<RadioGroup
86-
className="gui-yaml-switch"
87-
name="yaml-mode"
88-
initialTab={selectedTab}
89-
disabled={false}
90-
onChange={handleTabSwitch}
91-
>
92-
{filters.length &&
93-
filters.map(
94-
(filter: TabState, index: number) =>
95-
filter.count > 0 && (
96-
<RadioGroup.Radio value={filter.status}>
97-
{index !== 0 && (
98-
<span
99-
className={`dc__app-summary__icon icon-dim-16 mr-6 ${filter.status} ${filter.status}--node`}
100-
style={{ zIndex: 'unset' }}
101-
/>
102-
)}
103-
<span className="dc__first-letter-capitalize">{filter.status}</span>
104-
<span className="pl-4">({filter.count})</span>
105-
</RadioGroup.Radio>
106-
),
107-
)}
108-
</RadioGroup>
121+
<>
122+
<RadioGroup
123+
className={`gui-yaml-switch status-filter-button ${overflowFilters ? 'with-menu-button' : ''}`}
124+
name="status-filter-button"
125+
initialTab={selectedTab}
126+
disabled={false}
127+
onChange={handleTabSwitch}
128+
>
129+
{displayedInlineFilters.map((filter, index) => (
130+
<RadioGroup.Radio
131+
key={filter.status}
132+
value={filter.status}
133+
showTippy={index !== 0}
134+
tippyPlacement="top"
135+
tippyContent={filter.status}
136+
tippyClass="w-100 dc__first-letter-capitalize"
137+
>
138+
{index !== 0 ? (
139+
<>
140+
<span
141+
className={`dc__app-summary__icon icon-dim-16 ${filter.status} ${filter.status}--node`}
142+
style={{ zIndex: 'unset' }}
143+
/>
144+
<span>{filter.count}</span>
145+
</>
146+
) : (
147+
<span className="dc__first-letter-capitalize">{`${filter.status} (${filter.count})`}</span>
148+
)}
149+
</RadioGroup.Radio>
150+
))}
151+
</RadioGroup>
152+
{renderOverflowFilters()}
153+
</>
109154
)
110155
}

src/Shared/Components/TabGroup/TabGroup.component.tsx

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { Link, NavLink } from 'react-router-dom'
22

3-
import { ReactComponent as ICErrorExclamation } from '@Icons/ic-error-exclamation.svg'
4-
import { ReactComponent as ICWarning } from '@Icons/ic-warning.svg'
53
import { ComponentSizeType } from '@Shared/constants'
64

75
import { TabGroupProps, TabProps } from './TabGroup.types'
6+
import { getClassNameBySizeMap, tabGroupClassMap } from './TabGroup.utils'
7+
import { getTabBadge, getTabDescription, getTabIcon, getTabIndicator } from './TabGroup.helpers'
8+
89
import './TabGroup.scss'
9-
import { getClassNameBySizeMap, getIconColorClassMap } from './TabGroup.utils'
1010

1111
const Tab = ({
1212
label,
1313
props,
1414
tabType,
1515
active,
16-
icon: Icon,
17-
iconType = 'fill',
16+
icon,
1817
size,
1918
badge = null,
2019
alignActiveBorderWithContainer,
@@ -23,6 +22,7 @@ const Tab = ({
2322
showError,
2423
showWarning,
2524
disabled,
25+
description,
2626
}: TabProps & Pick<TabGroupProps, 'size' | 'alignActiveBorderWithContainer' | 'hideTopPadding'>) => {
2727
const { tabClassName, iconClassName, badgeClassName } = getClassNameBySizeMap({
2828
hideTopPadding,
@@ -32,24 +32,21 @@ const Tab = ({
3232
const getTabComponent = () => {
3333
const content = (
3434
<>
35-
{showError && <ICErrorExclamation className={`${iconClassName}`} />}
36-
{!showError && showWarning && <ICWarning className={`${iconClassName} warning-icon-y7`} />}
37-
{!showError && !showWarning && Icon && (
38-
<Icon className={`${iconClassName} ${getIconColorClassMap({ active })[iconType] || ''}`} />
39-
)}
40-
{label}
41-
{badge !== null && (
42-
<div className={`tab-group__tab__badge bcn-1 cn-7 fw-6 flex px-4 ${badgeClassName}`}>{badge}</div>
43-
)}
44-
{showIndicator && <span className="tab-group__tab__indicator bcr-5 mt-4 dc__align-self-start" />}
35+
<p className="m-0 flexbox dc__align-items-center dc__gap-6">
36+
{getTabIcon({ className: iconClassName, icon, showError, showWarning })}
37+
{label}
38+
{getTabBadge(badge, badgeClassName)}
39+
{getTabIndicator(showIndicator)}
40+
</p>
41+
{getTabDescription(description)}
4542
</>
4643
)
4744

4845
switch (tabType) {
4946
case 'link':
5047
return (
5148
<Link
52-
className={`${tabClassName} dc__no-decor flexbox dc__align-items-center dc__gap-6 ${disabled ? 'cursor-not-allowed' : ''}`}
49+
className={`${tabClassName} dc__no-decor flexbox-col ${disabled ? 'cursor-not-allowed' : ''}`}
5350
aria-disabled={disabled}
5451
{...props}
5552
>
@@ -59,18 +56,27 @@ const Tab = ({
5956
case 'navLink':
6057
return (
6158
<NavLink
62-
className={`${tabClassName} dc__no-decor tab-group__tab__nav-link flexbox dc__align-items-center dc__gap-6 ${disabled ? 'cursor-not-allowed' : ''}`}
59+
className={`${tabClassName} dc__no-decor flexbox-col tab-group__tab__nav-link ${disabled ? 'cursor-not-allowed' : ''}`}
6360
aria-disabled={disabled}
6461
{...props}
6562
>
6663
{content}
6764
</NavLink>
6865
)
66+
case 'block':
67+
return (
68+
<div
69+
className={`flexbox-col fw-6 ${tabClassName} ${disabled ? 'cursor-not-allowed' : ''}`}
70+
{...props}
71+
>
72+
{content}
73+
</div>
74+
)
6975
default:
7076
return (
7177
<button
7278
type="button"
73-
className={`dc__unset-button-styles ${tabClassName} flexbox dc__align-items-center dc__gap-6 ${disabled ? 'cursor-not-allowed' : ''}`}
79+
className={`dc__unset-button-styles flexbox-col ${tabClassName} ${disabled ? 'cursor-not-allowed' : ''}`}
7480
disabled={disabled}
7581
{...props}
7682
>
@@ -82,7 +88,7 @@ const Tab = ({
8288

8389
return (
8490
<li
85-
className={`tab-group__tab cursor lh-20 ${active ? 'tab-group__tab--active cb-5 fw-6' : 'cn-9 fw-4'} ${alignActiveBorderWithContainer ? 'tab-group__tab--align-active-border' : ''}`}
91+
className={`tab-group__tab lh-20 ${active ? 'tab-group__tab--active cb-5 fw-6' : 'cn-9 fw-4'} ${alignActiveBorderWithContainer ? 'tab-group__tab--align-active-border' : ''} ${tabType === 'block' ? 'tab-group__tab--block' : 'cursor'}`}
8692
>
8793
{getTabComponent()}
8894
</li>
@@ -96,11 +102,8 @@ export const TabGroup = ({
96102
alignActiveBorderWithContainer,
97103
hideTopPadding,
98104
}: TabGroupProps) => (
99-
<div className="flexbox dc__align-items-center dc__content-space">
100-
<ul
101-
role="tablist"
102-
className={`tab-group flexbox dc__align-items-center p-0 m-0 ${size === ComponentSizeType.large ? 'dc__gap-16' : 'dc__gap-12'}`}
103-
>
105+
<div className="flexbox dc__align-items-center dc__content-space">
106+
<ul role="tablist" className={`tab-group flexbox dc__align-items-center p-0 m-0 ${tabGroupClassMap[size]}`}>
104107
{tabs.map(({ id, ...resProps }) => (
105108
<Tab
106109
key={id}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ReactComponent as ICErrorExclamation } from '@Icons/ic-error-exclamation.svg'
2+
import { ReactComponent as ICWarning } from '@Icons/ic-warning.svg'
3+
4+
import { TabProps } from './TabGroup.types'
5+
6+
export const getTabIcon = ({
7+
icon: Icon,
8+
showError,
9+
showWarning,
10+
className,
11+
}: Pick<TabProps, 'showError' | 'showWarning' | 'icon'> & { className: string }) => {
12+
if (showError) {
13+
return <ICErrorExclamation className={className} />
14+
}
15+
if (showWarning) {
16+
return <ICWarning className={`${className} warning-icon-y7`} />
17+
}
18+
if (Icon) {
19+
return <Icon className={`${className} tab-group__tab__icon`} />
20+
}
21+
return null
22+
}
23+
24+
export const getTabBadge = (badge: TabProps['badge'], className: string) =>
25+
badge !== null && <div className={`tab-group__tab__badge bcn-1 cn-7 fw-6 flex px-4 ${className}`}>{badge}</div>
26+
27+
export const getTabIndicator = (showIndicator: TabProps['showIndicator']) =>
28+
showIndicator && <span className="tab-group__tab__indicator bcr-5 mt-4 dc__align-self-start" />
29+
30+
export const getTabDescription = (description: TabProps['description']) =>
31+
description && (
32+
<ul className="tab-group__tab__description m-0 p-0 fs-12 lh-16 fw-4 cn-7 flexbox dc__align-items-center dc__gap-4">
33+
{Array.isArray(description)
34+
? description.map((desc, idx) => (
35+
<li key={desc} className="flex dc__gap-4">
36+
{!!idx && <span className="dc__bullet" />}
37+
{desc}
38+
</li>
39+
))
40+
: description}
41+
</ul>
42+
)

0 commit comments

Comments
 (0)