Skip to content

Commit 71bb435

Browse files
authored
Merge pull request #716 from devtron-labs/feat/about-devtron
feat: about devtron dialog
2 parents f92ca67 + 986f418 commit 71bb435

File tree

13 files changed

+203
-77
lines changed

13 files changed

+203
-77
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.12.0-pre-2",
3+
"version": "1.12.0-pre-3",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import ReactGA from 'react-ga4'
2+
3+
import DevtronCopyright from '@Common/DevtronCopyright'
4+
import { EULA_LINK, PRIVACY_POLICY_LINK, TERMS_OF_USE_LINK } from '@Shared/constants'
5+
import { useMainContext } from '@Shared/Providers'
6+
7+
import { Button, ButtonComponentType, ButtonStyleType, ButtonVariantType } from '../Button'
8+
import { InstallationType } from '../Header/types'
9+
import { Icon } from '../Icon'
10+
11+
const AboutDevtronBody = ({ isFELibAvailable }: { isFELibAvailable: boolean }) => {
12+
const { currentServerInfo } = useMainContext()
13+
14+
const currentVersion = currentServerInfo?.serverInfo?.currentVersion
15+
const isEnterprise = currentServerInfo?.serverInfo?.installationType === InstallationType.ENTERPRISE
16+
17+
const isVersionCompatible = isFELibAvailable === isEnterprise
18+
19+
const handleEULAClick = () => {
20+
ReactGA.event({
21+
category: 'about-devtron',
22+
action: 'ABOUT_DEVTRON_LICENSE_AGREEMENT_CLICKED',
23+
})
24+
}
25+
26+
return (
27+
<div className="flexbox-col p-32 dc__gap-24 br-16 border__secondary bg__secondary">
28+
<div className="flexbox-col dc__align-items-center dc__gap-16 text-center">
29+
<div className="flex p-6 border__primary br-8">
30+
<Icon name="ic-devtron" color="B500" size={40} />
31+
</div>
32+
<div>
33+
<p className="fs-16 cn-9 fw-6 lh-1-5 m-0">Devtron</p>
34+
{isVersionCompatible && (
35+
<p className="fs-13 cn-7 fw-4 lh-20 m-0">{`${isEnterprise ? 'Enterprise' : 'OSS'} Version${currentVersion ? `(${currentVersion})` : ''}`}</p>
36+
)}
37+
</div>
38+
<DevtronCopyright />
39+
</div>
40+
<div className="flexbox flex-wrap dc__content-center dc__gap-4">
41+
<Button
42+
dataTestId="terms-of-service"
43+
text="Terms of service"
44+
variant={ButtonVariantType.text}
45+
style={ButtonStyleType.neutral}
46+
component={ButtonComponentType.anchor}
47+
anchorProps={{
48+
href: TERMS_OF_USE_LINK,
49+
}}
50+
/>
51+
<span></span>
52+
<Button
53+
dataTestId="privacy-policy"
54+
text="Privacy policy"
55+
variant={ButtonVariantType.text}
56+
style={ButtonStyleType.neutral}
57+
component={ButtonComponentType.anchor}
58+
anchorProps={{
59+
href: PRIVACY_POLICY_LINK,
60+
}}
61+
/>
62+
<span></span>
63+
<Button
64+
dataTestId="license-agreement"
65+
text="End-User License agreement"
66+
variant={ButtonVariantType.text}
67+
style={ButtonStyleType.neutral}
68+
onClick={handleEULAClick}
69+
component={ButtonComponentType.anchor}
70+
anchorProps={{
71+
href: EULA_LINK,
72+
}}
73+
/>
74+
</div>
75+
</div>
76+
)
77+
}
78+
79+
export default AboutDevtronBody
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ComponentSizeType } from '@Shared/constants'
2+
3+
import { Backdrop } from '../Backdrop'
4+
import { Button } from '../Button'
5+
import AboutDevtronBody from './AboutDevtronBody'
6+
7+
const AboutDevtronDialog = ({
8+
handleCloseLicenseInfoDialog,
9+
isFELibAvailable,
10+
}: {
11+
handleCloseLicenseInfoDialog: () => void
12+
isFELibAvailable: boolean
13+
}) => (
14+
<Backdrop onEscape={handleCloseLicenseInfoDialog}>
15+
<div className="flexbox-col w-400 br-12 bg__primary border__primary dc__m-auto mt-40">
16+
<div className="p-24">
17+
<AboutDevtronBody isFELibAvailable={isFELibAvailable} />
18+
</div>
19+
<div className="flex px-24 py-20 dc__content-end">
20+
<Button
21+
dataTestId="license-info-okay"
22+
text="Okay"
23+
size={ComponentSizeType.medium}
24+
onClick={handleCloseLicenseInfoDialog}
25+
/>
26+
</div>
27+
</div>
28+
</Backdrop>
29+
)
30+
31+
export default AboutDevtronDialog
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as AboutDevtronBody } from './AboutDevtronBody'
2+
export { default as AboutDevtronDialog } from './AboutDevtronDialog'

src/Shared/Components/Header/HelpNav.tsx

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const HelpNav = ({
7676
onClickHelpOptions(option)
7777
}
7878

79-
const handleOpenLicenseDialog = () => {
79+
const handleOpenAboutDevtron = () => {
8080
ReactGA.event({
8181
category: 'help-nav__about-devtron',
8282
action: 'ABOUT_DEVTRON_CLICKED',
@@ -100,23 +100,22 @@ const HelpNav = ({
100100
<option.icon />
101101
<div className="ml-12 cn-9 fs-14">{option.name}</div>
102102
</a>
103-
{/* licenseData is only set when showLicenseData is received true */}
104-
{isEnterprise && index === 1 && (
103+
{index === 1 && (
105104
<>
106-
{licenseData && (
107-
<button
108-
type="button"
109-
className="dc__transparent help-card__option flexbox dc__align-items-center cn-9 dc__gap-12 fs-14"
110-
onClick={handleOpenLicenseDialog}
111-
data-testid="about-devtron"
112-
>
113-
<Icon name="ic-devtron" color="N600" size={20} />
114-
About Devtron
115-
</button>
105+
<button
106+
type="button"
107+
className="dc__transparent help-card__option flexbox dc__align-items-center cn-9 dc__gap-12 fs-14"
108+
onClick={handleOpenAboutDevtron}
109+
data-testid="about-devtron"
110+
>
111+
<Icon name="ic-devtron" color="N600" size={20} />
112+
About Devtron
113+
</button>
114+
{isEnterprise && (
115+
<div className="help__enterprise pl-8 pb-4-imp pt-4-imp dc__gap-12 flexbox dc__align-items-center h-28">
116+
Enterprise Support
117+
</div>
116118
)}
117-
<div className="help__enterprise pl-8 pb-4-imp pt-4-imp dc__gap-12 flexbox dc__align-items-center h-28">
118-
Enterprise Support
119-
</div>
120119
</>
121120
)}
122121
</Fragment>

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

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,23 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { Link, NavLink } from 'react-router-dom'
17+
import { useMemo } from 'react'
18+
import { Link, NavLink, useRouteMatch } from 'react-router-dom'
19+
import { motion } from 'framer-motion'
1820

1921
import { Tooltip } from '@Common/Tooltip'
2022
import { ComponentSizeType } from '@Shared/constants'
2123

22-
import { getTabBadge, getTabDescription, getTabIcon, getTabIndicator } from './TabGroup.helpers'
23-
import { TabGroupProps, TabProps } from './TabGroup.types'
24+
import { getPathnameToMatch, getTabBadge, getTabDescription, getTabIcon, getTabIndicator } from './TabGroup.helpers'
25+
import { AdditionalTabProps, TabGroupProps, TabProps } from './TabGroup.types'
2426
import { getClassNameBySizeMap, tabGroupClassMap } from './TabGroup.utils'
2527

2628
import './TabGroup.scss'
2729

30+
const MotionLayoutUnderline = ({ layoutId }: { layoutId: string }) => (
31+
<motion.div layout="position" layoutId={layoutId} className="underline bcb-5" />
32+
)
33+
2834
const Tab = ({
2935
label,
3036
props,
@@ -33,7 +39,6 @@ const Tab = ({
3339
icon,
3440
size,
3541
badge = null,
36-
alignActiveBorderWithContainer,
3742
hideTopPadding,
3843
showIndicator,
3944
showError,
@@ -42,10 +47,19 @@ const Tab = ({
4247
description,
4348
shouldWrapTooltip,
4449
tooltipProps,
45-
}: TabProps & Pick<TabGroupProps, 'size' | 'alignActiveBorderWithContainer' | 'hideTopPadding'>) => {
50+
uniqueGroupId,
51+
}: TabProps & Pick<TabGroupProps, 'size' | 'hideTopPadding'> & AdditionalTabProps) => {
52+
const { path } = useRouteMatch()
53+
const pathToMatch = tabType === 'navLink' || tabType === 'link' ? getPathnameToMatch(props.to, path) : ''
54+
55+
// using match to define if tab is active as useRouteMatch return an object if path is matched otherwise return null/undefined
56+
const match = useRouteMatch(pathToMatch)
57+
58+
const isTabActive = tabType === 'button' ? active : !!match
59+
4660
const { tabClassName, iconClassName, badgeClassName } = getClassNameBySizeMap({
4761
hideTopPadding,
48-
alignActiveBorderWithContainer,
62+
isTabActive,
4963
})[size]
5064

5165
const onClickHandler = (
@@ -121,9 +135,10 @@ const Tab = ({
121135

122136
const renderTabContainer = () => (
123137
<li
124-
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' : ''} ${disabled ? 'dc__disabled' : 'cursor'}`}
138+
className={`tab-group__tab lh-20 ${active ? 'cb-5 fw-6' : 'cn-9 fw-4'} ${tabType === 'block' ? 'tab-group__tab--block' : ''} ${disabled ? 'dc__disabled' : 'cursor'}`}
125139
>
126140
{getTabComponent()}
141+
{isTabActive && <MotionLayoutUnderline layoutId={uniqueGroupId} />}
127142
</li>
128143
)
129144

@@ -138,22 +153,27 @@ export const TabGroup = ({
138153
tabs = [],
139154
size = ComponentSizeType.large,
140155
rightComponent,
141-
alignActiveBorderWithContainer,
142156
hideTopPadding,
143-
}: TabGroupProps) => (
144-
<div className="flexbox dc__align-items-center dc__content-space">
145-
<ul role="tablist" className={`tab-group flexbox dc__align-items-center p-0 m-0 ${tabGroupClassMap[size]}`}>
146-
{tabs.map(({ id, ...resProps }) => (
147-
<Tab
148-
key={id}
149-
id={id}
150-
size={size}
151-
alignActiveBorderWithContainer={alignActiveBorderWithContainer}
152-
hideTopPadding={hideTopPadding}
153-
{...resProps}
154-
/>
155-
))}
156-
</ul>
157-
{rightComponent || null}
158-
</div>
159-
)
157+
}: TabGroupProps) => {
158+
// Unique layoutId for motion.div to handle multiple tab groups on same page
159+
// Using tab labels so that id remains same on re mount as well
160+
const uniqueGroupId = useMemo(() => tabs.map((tab) => tab.label).join('-'), [])
161+
162+
return (
163+
<div className="flexbox dc__align-items-center dc__content-space">
164+
<ul role="tablist" className={`tab-group flexbox dc__align-items-center p-0 m-0 ${tabGroupClassMap[size]}`}>
165+
{tabs.map(({ id, ...resProps }) => (
166+
<Tab
167+
key={id}
168+
id={id}
169+
size={size}
170+
hideTopPadding={hideTopPadding}
171+
uniqueGroupId={uniqueGroupId}
172+
{...resProps}
173+
/>
174+
))}
175+
</ul>
176+
{rightComponent || null}
177+
</div>
178+
)
179+
}

src/Shared/Components/TabGroup/TabGroup.helpers.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { LinkProps, NavLinkProps } from 'react-router-dom'
18+
1719
import { ReactComponent as ICErrorExclamation } from '@Icons/ic-error-exclamation.svg'
1820
import { ReactComponent as ICWarning } from '@Icons/ic-warning.svg'
1921

@@ -65,3 +67,14 @@ export const getTabDescription = (description: TabProps['description']) =>
6567
: description}
6668
</ul>
6769
)
70+
71+
const replaceTrailingSlash = (pathname: string) => pathname.replace(/\/+$/, '')
72+
73+
export const getPathnameToMatch = (to: NavLinkProps['to'] | LinkProps['to'], currentPathname: string): string => {
74+
if (typeof to === 'string' || (to && typeof to === 'object' && 'pathname' in to)) {
75+
const pathname = typeof to === 'string' ? to : to.pathname || ''
76+
// handling absolute and relative paths
77+
return pathname.startsWith('/') ? pathname : `${replaceTrailingSlash(currentPathname)}/${pathname}`
78+
}
79+
return ''
80+
}

src/Shared/Components/TabGroup/TabGroup.scss

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,14 @@
3333

3434
@include svg-styles(var(--N700));
3535

36-
&::after {
37-
content: '';
38-
position: absolute;
39-
bottom: 0;
40-
left: 0;
41-
width: 100%;
36+
.underline {
4237
height: 2px;
43-
background-color: transparent;
4438
border-top-left-radius: 2px;
4539
border-top-right-radius: 2px;
4640
}
4741

48-
&--align-active-border::after {
49-
bottom: -1px;
42+
&--active {
43+
@include svg-styles(var(--B500));
5044
}
5145

5246
&:hover:not(.tab-group__tab--block):not(.dc__disabled) {
@@ -58,14 +52,6 @@
5852
}
5953
}
6054

61-
&--active {
62-
@include svg-styles(var(--B500));
63-
64-
&::after {
65-
background-color: var(--B500);
66-
}
67-
}
68-
6955
&__badge {
7056
border-radius: 10px;
7157
min-width: 20px;
@@ -104,11 +90,5 @@
10490
color: var(--B500);
10591
}
10692
}
107-
108-
&:has(.active) {
109-
&::after {
110-
background-color: var(--B500);
111-
}
112-
}
11393
}
11494
}

src/Shared/Components/TabGroup/TabGroup.types.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,13 @@ export interface TabGroupProps {
152152
* Optional component to be rendered on the right side of the tab list.
153153
*/
154154
rightComponent?: React.ReactElement
155-
/**
156-
* Set to `true` to align the active tab's border with the bottom border of the parent container.
157-
* @default false
158-
*/
159-
alignActiveBorderWithContainer?: boolean
160155
/**
161156
* Determines if the top padding of the tab group should be hidden.
162157
* @default false
163158
*/
164159
hideTopPadding?: boolean
165160
}
161+
162+
export type AdditionalTabProps = {
163+
uniqueGroupId: string
164+
}

0 commit comments

Comments
 (0)