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 && ( - + + {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',