diff --git a/package-lock.json b/package-lock.json
index 9497b7dab..386347e6d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@devtron-labs/devtron-fe-common-lib",
- "version": "1.12.0-pre-2",
+ "version": "1.12.0-pre-3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@devtron-labs/devtron-fe-common-lib",
- "version": "1.12.0-pre-2",
+ "version": "1.12.0-pre-3",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
diff --git a/package.json b/package.json
index 8dfbd6547..77e914d22 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@devtron-labs/devtron-fe-common-lib",
- "version": "1.12.0-pre-2",
+ "version": "1.12.0-pre-3",
"description": "Supporting common component library",
"type": "module",
"main": "dist/index.js",
diff --git a/src/Shared/Components/AboutDevtron/AboutDevtronBody.tsx b/src/Shared/Components/AboutDevtron/AboutDevtronBody.tsx
new file mode 100644
index 000000000..f5bd435de
--- /dev/null
+++ b/src/Shared/Components/AboutDevtron/AboutDevtronBody.tsx
@@ -0,0 +1,79 @@
+import ReactGA from 'react-ga4'
+
+import DevtronCopyright from '@Common/DevtronCopyright'
+import { EULA_LINK, PRIVACY_POLICY_LINK, TERMS_OF_USE_LINK } from '@Shared/constants'
+import { useMainContext } from '@Shared/Providers'
+
+import { Button, ButtonComponentType, ButtonStyleType, ButtonVariantType } from '../Button'
+import { InstallationType } from '../Header/types'
+import { Icon } from '../Icon'
+
+const AboutDevtronBody = ({ isFELibAvailable }: { isFELibAvailable: boolean }) => {
+ const { currentServerInfo } = useMainContext()
+
+ const currentVersion = currentServerInfo?.serverInfo?.currentVersion
+ const isEnterprise = currentServerInfo?.serverInfo?.installationType === InstallationType.ENTERPRISE
+
+ const isVersionCompatible = isFELibAvailable === isEnterprise
+
+ const handleEULAClick = () => {
+ ReactGA.event({
+ category: 'about-devtron',
+ action: 'ABOUT_DEVTRON_LICENSE_AGREEMENT_CLICKED',
+ })
+ }
+
+ return (
+
+
+
+
+
+
+
Devtron
+ {isVersionCompatible && (
+
{`${isEnterprise ? 'Enterprise' : 'OSS'} Version${currentVersion ? `(${currentVersion})` : ''}`}
+ )}
+
+
+
+
+
+ •
+
+ •
+
+
+
+ )
+}
+
+export default AboutDevtronBody
diff --git a/src/Shared/Components/AboutDevtron/AboutDevtronDialog.tsx b/src/Shared/Components/AboutDevtron/AboutDevtronDialog.tsx
new file mode 100644
index 000000000..1b48138e6
--- /dev/null
+++ b/src/Shared/Components/AboutDevtron/AboutDevtronDialog.tsx
@@ -0,0 +1,31 @@
+import { ComponentSizeType } from '@Shared/constants'
+
+import { Backdrop } from '../Backdrop'
+import { Button } from '../Button'
+import AboutDevtronBody from './AboutDevtronBody'
+
+const AboutDevtronDialog = ({
+ handleCloseLicenseInfoDialog,
+ isFELibAvailable,
+}: {
+ handleCloseLicenseInfoDialog: () => void
+ isFELibAvailable: boolean
+}) => (
+
+
+
+)
+
+export default AboutDevtronDialog
diff --git a/src/Shared/Components/AboutDevtron/index.tsx b/src/Shared/Components/AboutDevtron/index.tsx
new file mode 100644
index 000000000..70550e75a
--- /dev/null
+++ b/src/Shared/Components/AboutDevtron/index.tsx
@@ -0,0 +1,2 @@
+export { default as AboutDevtronBody } from './AboutDevtronBody'
+export { default as AboutDevtronDialog } from './AboutDevtronDialog'
diff --git a/src/Shared/Components/Header/HelpNav.tsx b/src/Shared/Components/Header/HelpNav.tsx
index 1c6cc2251..0916f53f9 100644
--- a/src/Shared/Components/Header/HelpNav.tsx
+++ b/src/Shared/Components/Header/HelpNav.tsx
@@ -76,7 +76,7 @@ const HelpNav = ({
onClickHelpOptions(option)
}
- const handleOpenLicenseDialog = () => {
+ const handleOpenAboutDevtron = () => {
ReactGA.event({
category: 'help-nav__about-devtron',
action: 'ABOUT_DEVTRON_CLICKED',
@@ -100,23 +100,22 @@ const HelpNav = ({
{option.name}
- {/* licenseData is only set when showLicenseData is received true */}
- {isEnterprise && index === 1 && (
+ {index === 1 && (
<>
- {licenseData && (
-
-
- About Devtron
-
+
+
+ About Devtron
+
+ {isEnterprise && (
+
+ Enterprise Support
+
)}
-
- Enterprise Support
-
>
)}
diff --git a/src/Shared/Components/TabGroup/TabGroup.component.tsx b/src/Shared/Components/TabGroup/TabGroup.component.tsx
index 801a4d806..d7bcba5f8 100644
--- a/src/Shared/Components/TabGroup/TabGroup.component.tsx
+++ b/src/Shared/Components/TabGroup/TabGroup.component.tsx
@@ -14,17 +14,23 @@
* limitations under the License.
*/
-import { Link, NavLink } from 'react-router-dom'
+import { useMemo } from 'react'
+import { Link, NavLink, useRouteMatch } from 'react-router-dom'
+import { motion } from 'framer-motion'
import { Tooltip } from '@Common/Tooltip'
import { ComponentSizeType } from '@Shared/constants'
-import { getTabBadge, getTabDescription, getTabIcon, getTabIndicator } from './TabGroup.helpers'
-import { TabGroupProps, TabProps } from './TabGroup.types'
+import { getPathnameToMatch, getTabBadge, getTabDescription, getTabIcon, getTabIndicator } from './TabGroup.helpers'
+import { AdditionalTabProps, TabGroupProps, TabProps } from './TabGroup.types'
import { getClassNameBySizeMap, tabGroupClassMap } from './TabGroup.utils'
import './TabGroup.scss'
+const MotionLayoutUnderline = ({ layoutId }: { layoutId: string }) => (
+
+)
+
const Tab = ({
label,
props,
@@ -33,7 +39,6 @@ const Tab = ({
icon,
size,
badge = null,
- alignActiveBorderWithContainer,
hideTopPadding,
showIndicator,
showError,
@@ -42,10 +47,19 @@ const Tab = ({
description,
shouldWrapTooltip,
tooltipProps,
-}: TabProps & Pick) => {
+ uniqueGroupId,
+}: TabProps & Pick & AdditionalTabProps) => {
+ const { path } = useRouteMatch()
+ const pathToMatch = tabType === 'navLink' || tabType === 'link' ? getPathnameToMatch(props.to, path) : ''
+
+ // using match to define if tab is active as useRouteMatch return an object if path is matched otherwise return null/undefined
+ const match = useRouteMatch(pathToMatch)
+
+ const isTabActive = tabType === 'button' ? active : !!match
+
const { tabClassName, iconClassName, badgeClassName } = getClassNameBySizeMap({
hideTopPadding,
- alignActiveBorderWithContainer,
+ isTabActive,
})[size]
const onClickHandler = (
@@ -121,9 +135,10 @@ const Tab = ({
const renderTabContainer = () => (
{getTabComponent()}
+ {isTabActive && }
)
@@ -138,22 +153,27 @@ export const TabGroup = ({
tabs = [],
size = ComponentSizeType.large,
rightComponent,
- alignActiveBorderWithContainer,
hideTopPadding,
-}: TabGroupProps) => (
-
-
- {tabs.map(({ id, ...resProps }) => (
-
- ))}
-
- {rightComponent || null}
-
-)
+}: TabGroupProps) => {
+ // Unique layoutId for motion.div to handle multiple tab groups on same page
+ // Using tab labels so that id remains same on re mount as well
+ const uniqueGroupId = useMemo(() => tabs.map((tab) => tab.label).join('-'), [])
+
+ return (
+
+
+ {tabs.map(({ id, ...resProps }) => (
+
+ ))}
+
+ {rightComponent || null}
+
+ )
+}
diff --git a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx
index 3d515b910..9aec1c1da 100644
--- a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx
+++ b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import { LinkProps, NavLinkProps } from 'react-router-dom'
+
import { ReactComponent as ICErrorExclamation } from '@Icons/ic-error-exclamation.svg'
import { ReactComponent as ICWarning } from '@Icons/ic-warning.svg'
@@ -65,3 +67,14 @@ export const getTabDescription = (description: TabProps['description']) =>
: description}
)
+
+const replaceTrailingSlash = (pathname: string) => pathname.replace(/\/+$/, '')
+
+export const getPathnameToMatch = (to: NavLinkProps['to'] | LinkProps['to'], currentPathname: string): string => {
+ if (typeof to === 'string' || (to && typeof to === 'object' && 'pathname' in to)) {
+ const pathname = typeof to === 'string' ? to : to.pathname || ''
+ // handling absolute and relative paths
+ return pathname.startsWith('/') ? pathname : `${replaceTrailingSlash(currentPathname)}/${pathname}`
+ }
+ return ''
+}
diff --git a/src/Shared/Components/TabGroup/TabGroup.scss b/src/Shared/Components/TabGroup/TabGroup.scss
index c5a352898..2ddaaf6b9 100644
--- a/src/Shared/Components/TabGroup/TabGroup.scss
+++ b/src/Shared/Components/TabGroup/TabGroup.scss
@@ -33,20 +33,14 @@
@include svg-styles(var(--N700));
- &::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
+ .underline {
height: 2px;
- background-color: transparent;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
- &--align-active-border::after {
- bottom: -1px;
+ &--active {
+ @include svg-styles(var(--B500));
}
&:hover:not(.tab-group__tab--block):not(.dc__disabled) {
@@ -58,14 +52,6 @@
}
}
- &--active {
- @include svg-styles(var(--B500));
-
- &::after {
- background-color: var(--B500);
- }
- }
-
&__badge {
border-radius: 10px;
min-width: 20px;
@@ -104,11 +90,5 @@
color: var(--B500);
}
}
-
- &:has(.active) {
- &::after {
- background-color: var(--B500);
- }
- }
}
}
diff --git a/src/Shared/Components/TabGroup/TabGroup.types.ts b/src/Shared/Components/TabGroup/TabGroup.types.ts
index 0ddb160f9..022b83254 100644
--- a/src/Shared/Components/TabGroup/TabGroup.types.ts
+++ b/src/Shared/Components/TabGroup/TabGroup.types.ts
@@ -152,14 +152,13 @@ export interface TabGroupProps {
* Optional component to be rendered on the right side of the tab list.
*/
rightComponent?: React.ReactElement
- /**
- * Set to `true` to align the active tab's border with the bottom border of the parent container.
- * @default false
- */
- alignActiveBorderWithContainer?: boolean
/**
* Determines if the top padding of the tab group should be hidden.
* @default false
*/
hideTopPadding?: boolean
}
+
+export type AdditionalTabProps = {
+ uniqueGroupId: string
+}
diff --git a/src/Shared/Components/TabGroup/TabGroup.utils.ts b/src/Shared/Components/TabGroup/TabGroup.utils.ts
index e2f73bb93..5bc8aeb6f 100644
--- a/src/Shared/Components/TabGroup/TabGroup.utils.ts
+++ b/src/Shared/Components/TabGroup/TabGroup.utils.ts
@@ -21,8 +21,8 @@ import { TabGroupProps } from './TabGroup.types'
export const getClassNameBySizeMap = ({
hideTopPadding,
- alignActiveBorderWithContainer,
-}: Pick): Record<
+ isTabActive,
+}: Pick & { isTabActive: boolean }): Record<
TabGroupProps['size'],
{
tabClassName: string
@@ -31,17 +31,17 @@ export const getClassNameBySizeMap = ({
}
> => ({
[ComponentSizeType.medium]: {
- tabClassName: `fs-12 ${!hideTopPadding ? 'pt-6' : ''} ${alignActiveBorderWithContainer ? 'pb-5' : 'pb-6'}`,
+ tabClassName: `fs-12 ${!hideTopPadding ? 'pt-6' : ''} ${isTabActive ? 'pb-3' : 'pb-5'}`,
iconClassName: 'icon-dim-14',
badgeClassName: 'fs-11 lh-18 tab-group__tab__badge--medium',
},
[ComponentSizeType.large]: {
- tabClassName: `fs-13 ${!hideTopPadding ? 'pt-8' : ''} ${alignActiveBorderWithContainer ? 'pb-7' : 'pb-8'}`,
+ tabClassName: `fs-13 ${!hideTopPadding ? 'pt-8' : ''} ${isTabActive ? 'pb-5' : 'pb-7'}`,
iconClassName: 'icon-dim-16',
badgeClassName: 'fs-12 lh-20',
},
[ComponentSizeType.xl]: {
- tabClassName: `min-w-200 fs-13 ${!hideTopPadding ? 'pt-10' : ''} ${alignActiveBorderWithContainer ? 'pb-9' : 'pb-10'}`,
+ tabClassName: `min-w-200 fs-13 ${!hideTopPadding ? 'pt-10' : ''} ${isTabActive ? 'pb-7' : 'pb-9'}`,
iconClassName: 'icon-dim-16',
badgeClassName: 'fs-12 lh-20',
},
diff --git a/src/Shared/Components/index.ts b/src/Shared/Components/index.ts
index 14503779c..75d7f961e 100644
--- a/src/Shared/Components/index.ts
+++ b/src/Shared/Components/index.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+export * from './AboutDevtron'
export * from './ActionMenu'
export * from './ActivityIndicator'
export * from './AnimatedDeployButton'
diff --git a/src/Shared/constants.tsx b/src/Shared/constants.tsx
index ae2c2d9be..d53848f04 100644
--- a/src/Shared/constants.tsx
+++ b/src/Shared/constants.tsx
@@ -538,6 +538,8 @@ export const DC_DELETE_SUBTITLES = {
export const EULA_LINK = 'https://devtron.ai/end-user-license-agreement-eula'
export const CONTACT_SUPPORT_LINK = 'https://devtron.ai/enterprise-support'
+export const PRIVACY_POLICY_LINK = 'https://devtron.ai/privacy-policy'
+export const TERMS_OF_USE_LINK = 'https://devtron.ai/terms-of-use'
export const enum DeleteComponentsName {
Cluster = 'cluster',