From bcd1a3d2c0db6a5990555737c2bc7d00baf826f5 Mon Sep 17 00:00:00 2001 From: Amrit Kashyap Borah Date: Fri, 23 May 2025 12:53:54 +0530 Subject: [PATCH 01/11] feat: add icon & support for svg className in draggable button --- src/Assets/IconV2/ic-paper-plane.svg | 3 +++ src/Assets/IconV2/ic-sparkle-color.svg | 19 ++++++++++++++++++- .../DraggableWrapper/DraggableButton.tsx | 4 ++-- src/Common/DraggableWrapper/types.ts | 1 + src/Shared/Components/Icon/Icon.tsx | 2 ++ src/index.ts | 1 + 6 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/Assets/IconV2/ic-paper-plane.svg diff --git a/src/Assets/IconV2/ic-paper-plane.svg b/src/Assets/IconV2/ic-paper-plane.svg new file mode 100644 index 000000000..f0015526d --- /dev/null +++ b/src/Assets/IconV2/ic-paper-plane.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/IconV2/ic-sparkle-color.svg b/src/Assets/IconV2/ic-sparkle-color.svg index a2aa5c5d3..5629a5d5a 100644 --- a/src/Assets/IconV2/ic-sparkle-color.svg +++ b/src/Assets/IconV2/ic-sparkle-color.svg @@ -1 +1,18 @@ - \ No newline at end of file + + + + diff --git a/src/Common/DraggableWrapper/DraggableButton.tsx b/src/Common/DraggableWrapper/DraggableButton.tsx index 48349887e..cb949d618 100644 --- a/src/Common/DraggableWrapper/DraggableButton.tsx +++ b/src/Common/DraggableWrapper/DraggableButton.tsx @@ -18,13 +18,13 @@ import React from 'react' import { ReactComponent as ICDrag } from '../../Assets/Icon/ic-drag.svg' import { DraggableButtonProps } from './types' -export default function DraggableButton({ dragClassName }: DraggableButtonProps) { +export default function DraggableButton({ dragClassName, svgClassName = 'fcn-6' }: DraggableButtonProps) { return ( ) } diff --git a/src/Common/DraggableWrapper/types.ts b/src/Common/DraggableWrapper/types.ts index 91b9f5f82..ccbf3b320 100644 --- a/src/Common/DraggableWrapper/types.ts +++ b/src/Common/DraggableWrapper/types.ts @@ -49,4 +49,5 @@ export interface DraggableWrapperProps { */ export interface DraggableButtonProps { dragClassName: string + svgClassName?: string } diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx index c3953b9fa..f7c49dc55 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -119,6 +119,7 @@ import { ReactComponent as ICOpenBox } from '@IconsV2/ic-open-box.svg' import { ReactComponent as ICOpenInNew } from '@IconsV2/ic-open-in-new.svg' import { ReactComponent as ICOpenshift } from '@IconsV2/ic-openshift.svg' import { ReactComponent as ICOutOfSync } from '@IconsV2/ic-out-of-sync.svg' +import { ReactComponent as ICPaperPlane } from '@IconsV2/ic-paper-plane.svg' import { ReactComponent as ICPaperPlaneColor } from '@IconsV2/ic-paper-plane-color.svg' import { ReactComponent as ICPath } from '@IconsV2/ic-path.svg' import { ReactComponent as ICPencil } from '@IconsV2/ic-pencil.svg' @@ -277,6 +278,7 @@ export const iconMap = { 'ic-openshift': ICOpenshift, 'ic-out-of-sync': ICOutOfSync, 'ic-paper-plane-color': ICPaperPlaneColor, + 'ic-paper-plane': ICPaperPlane, 'ic-path': ICPath, 'ic-pencil': ICPencil, 'ic-quay': ICQuay, diff --git a/src/index.ts b/src/index.ts index 2a0afb927..80769b942 100644 --- a/src/index.ts +++ b/src/index.ts @@ -155,6 +155,7 @@ export interface customEnv { GATEKEEPER_URL?: string FEATURE_AI_INTEGRATION_ENABLE?: boolean LOGIN_PAGE_IMAGE?: string + FEATURE_AI_APP_DETAILS_ENABLE?: boolean } declare global { interface Window { From df80d378989b1df4e9f95457c7a304b21bf28d3e Mon Sep 17 00:00:00 2001 From: Amrit Kashyap Borah Date: Mon, 26 May 2025 15:18:15 +0530 Subject: [PATCH 02/11] feat: add aiAgentContext field to MainContext --- src/Shared/Providers/types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Shared/Providers/types.ts b/src/Shared/Providers/types.ts index 82f1630cb..fbc251af6 100644 --- a/src/Shared/Providers/types.ts +++ b/src/Shared/Providers/types.ts @@ -78,6 +78,11 @@ export interface MainContext { reloadVersionConfig: ReloadVersionConfigTypes intelligenceConfig: IntelligenceConfig setIntelligenceConfig: Dispatch> + aiAgentContext: { + path: string + context: Record + } + setAIAgentContext: (aiAgentContext: MainContext['aiAgentContext']) => void } export interface MainContextProviderProps { From fed7f51e3a520d03763cf43c035481a8be94257f Mon Sep 17 00:00:00 2001 From: Amrit Kashyap Borah Date: Tue, 3 Jun 2025 16:54:20 +0530 Subject: [PATCH 03/11] feat: add typewriter in shared/components --- src/Assets/IconV2/ic-brain.svg | 3 +++ .../Components/Button/Button.component.tsx | 21 ++++++++++++++++--- src/Shared/Components/Button/types.ts | 3 ++- src/Shared/Components/Icon/Icon.tsx | 2 ++ .../Textarea/Textarea.component.tsx | 2 +- .../Components/Typewriter/BlinkingCursor.tsx | 15 +++++++++++++ .../Typewriter/Typewriter.component.tsx | 20 ++++++++++++++++++ src/Shared/Components/Typewriter/index.ts | 3 +++ .../Components/Typewriter/useTypewriter.ts | 20 ++++++++++++++++++ src/Shared/Components/index.ts | 1 + 10 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/Assets/IconV2/ic-brain.svg create mode 100644 src/Shared/Components/Typewriter/BlinkingCursor.tsx create mode 100644 src/Shared/Components/Typewriter/Typewriter.component.tsx create mode 100644 src/Shared/Components/Typewriter/index.ts create mode 100644 src/Shared/Components/Typewriter/useTypewriter.ts diff --git a/src/Assets/IconV2/ic-brain.svg b/src/Assets/IconV2/ic-brain.svg new file mode 100644 index 000000000..a5ff6cc35 --- /dev/null +++ b/src/Assets/IconV2/ic-brain.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Shared/Components/Button/Button.component.tsx b/src/Shared/Components/Button/Button.component.tsx index 525f96440..571ac5a50 100644 --- a/src/Shared/Components/Button/Button.component.tsx +++ b/src/Shared/Components/Button/Button.component.tsx @@ -34,6 +34,7 @@ const ButtonElement = ({ buttonProps, onClick, elementRef, + buttonRef, ...props }: PropsWithChildren< Omit< @@ -60,6 +61,20 @@ const ButtonElement = ({ // Added the specific class to ensure that the link override is applied const linkOrAnchorClassName = `${props.className} button__link ${props.disabled ? 'dc__disable-click' : ''}` + const refCallback = (el: HTMLButtonElement | HTMLAnchorElement) => { + if (!el) { + return + } + + // eslint-disable-next-line no-param-reassign + elementRef.current = el + + if (buttonRef && typeof buttonRef === 'object' && Object.hasOwn(buttonRef, 'current')) { + // eslint-disable-next-line no-param-reassign + buttonRef.current = el + } + } + if (component === ButtonComponentType.link) { return ( ['onClick']} - ref={elementRef as MutableRefObject} + ref={refCallback} /> ) } @@ -81,7 +96,7 @@ const ButtonElement = ({ {...props} className={linkOrAnchorClassName} onClick={onClick as ButtonProps['onClick']} - ref={elementRef as MutableRefObject} + ref={refCallback} > {props.children} @@ -95,7 +110,7 @@ const ButtonElement = ({ // eslint-disable-next-line react/button-has-type type={buttonProps?.type || 'button'} onClick={onClick as ButtonProps['onClick']} - ref={elementRef as MutableRefObject} + ref={refCallback} /> ) } diff --git a/src/Shared/Components/Button/types.ts b/src/Shared/Components/Button/types.ts index 5ed004be5..e4d532434 100644 --- a/src/Shared/Components/Button/types.ts +++ b/src/Shared/Components/Button/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactElement } from 'react' +import { AnchorHTMLAttributes, ButtonHTMLAttributes, MutableRefObject, ReactElement } from 'react' import { LinkProps } from 'react-router-dom' import { TooltipProps } from '@Common/Tooltip/types' @@ -141,6 +141,7 @@ export type ButtonProps } & ( | { /** diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx index b581d561b..4c15f4fd0 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -18,6 +18,7 @@ import { ReactComponent as ICBgCluster } from '@IconsV2/ic-bg-cluster.svg' import { ReactComponent as ICBharatpe } from '@IconsV2/ic-bharatpe.svg' import { ReactComponent as ICBitbucket } from '@IconsV2/ic-bitbucket.svg' import { ReactComponent as ICBookOpen } from '@IconsV2/ic-book-open.svg' +import { ReactComponent as ICBrain } from '@IconsV2/ic-brain.svg' import { ReactComponent as ICBrowser } from '@IconsV2/ic-browser.svg' import { ReactComponent as ICBuildColor } from '@IconsV2/ic-build-color.svg' import { ReactComponent as ICCalendar } from '@IconsV2/ic-calendar.svg' @@ -183,6 +184,7 @@ export const iconMap = { 'ic-bharatpe': ICBharatpe, 'ic-bitbucket': ICBitbucket, 'ic-book-open': ICBookOpen, + 'ic-brain': ICBrain, 'ic-browser': ICBrowser, 'ic-build-color': ICBuildColor, 'ic-calendar': ICCalendar, diff --git a/src/Shared/Components/Textarea/Textarea.component.tsx b/src/Shared/Components/Textarea/Textarea.component.tsx index fb9f3271e..703b9b062 100644 --- a/src/Shared/Components/Textarea/Textarea.component.tsx +++ b/src/Shared/Components/Textarea/Textarea.component.tsx @@ -116,7 +116,7 @@ const Textarea = ({ const handleKeyDown: TextareaHTMLAttributes['onKeyDown'] = ( event: React.KeyboardEvent, ) => { - if (event.key === 'Enter' || event.key === 'Escape') { + if ((event.key === 'Enter' && !event.metaKey && !event.ctrlKey) || event.key === 'Escape') { event.stopPropagation() if (event.key === 'Escape') { diff --git a/src/Shared/Components/Typewriter/BlinkingCursor.tsx b/src/Shared/Components/Typewriter/BlinkingCursor.tsx new file mode 100644 index 000000000..fa49c4a11 --- /dev/null +++ b/src/Shared/Components/Typewriter/BlinkingCursor.tsx @@ -0,0 +1,15 @@ +import { motion } from 'framer-motion' + +export const BlinkingCursor = () => ( + +) diff --git a/src/Shared/Components/Typewriter/Typewriter.component.tsx b/src/Shared/Components/Typewriter/Typewriter.component.tsx new file mode 100644 index 000000000..95c8513d1 --- /dev/null +++ b/src/Shared/Components/Typewriter/Typewriter.component.tsx @@ -0,0 +1,20 @@ +import { motion } from 'framer-motion' + +import { BlinkingCursor } from './BlinkingCursor' +import { useTypewriter } from './useTypewriter' + +interface TypewriterProps { + text: string +} + +export const Typewriter = ({ text }: TypewriterProps) => { + const visibleText = useTypewriter(text) + + return ( + + {visibleText} + + + + ) +} diff --git a/src/Shared/Components/Typewriter/index.ts b/src/Shared/Components/Typewriter/index.ts new file mode 100644 index 000000000..74c5f637e --- /dev/null +++ b/src/Shared/Components/Typewriter/index.ts @@ -0,0 +1,3 @@ +export { BlinkingCursor } from './BlinkingCursor' +export { Typewriter } from './Typewriter.component' +export { useTypewriter } from './useTypewriter' diff --git a/src/Shared/Components/Typewriter/useTypewriter.ts b/src/Shared/Components/Typewriter/useTypewriter.ts new file mode 100644 index 000000000..82ece9afd --- /dev/null +++ b/src/Shared/Components/Typewriter/useTypewriter.ts @@ -0,0 +1,20 @@ +import { useEffect } from 'react' +import { animate, useMotionValue, useTransform } from 'framer-motion' + +export const useTypewriter = (text: string) => { + const progress = useMotionValue(0) + + const visibleText = useTransform(progress, (latest) => text.slice(0, Math.floor(latest))) + + useEffect(() => { + const controls = animate(progress, text.length, { + type: 'tween', + duration: 4, + ease: 'linear', + }) + + return controls.stop + }, [text]) + + return visibleText +} diff --git a/src/Shared/Components/index.ts b/src/Shared/Components/index.ts index 261d398c5..79ddd2439 100644 --- a/src/Shared/Components/index.ts +++ b/src/Shared/Components/index.ts @@ -95,6 +95,7 @@ export * from './TargetPlatforms' export * from './Textarea' export * from './ThemeSwitcher' export * from './ToggleResolveScopedVariables' +export * from './Typewriter' export * from './UnsavedChanges' export * from './UnsavedChangesDialog' export * from './UserIdentifier' From 536973e1d5ea7a870831f72194967d0e4e297290 Mon Sep 17 00:00:00 2001 From: Amrit Kashyap Borah Date: Tue, 3 Jun 2025 17:47:33 +0530 Subject: [PATCH 04/11] feat: add state in sidePanelConfig --- src/Shared/Components/DocLink/DocLink.tsx | 4 +-- .../Components/Header/IframePromoButton.tsx | 4 +-- src/Shared/Components/Header/PageHeader.tsx | 28 +++++++++++++++++-- src/Shared/Providers/index.ts | 1 + src/Shared/Providers/types.ts | 16 +++++++++-- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/Shared/Components/DocLink/DocLink.tsx b/src/Shared/Components/DocLink/DocLink.tsx index a37450ee6..47f7e9173 100644 --- a/src/Shared/Components/DocLink/DocLink.tsx +++ b/src/Shared/Components/DocLink/DocLink.tsx @@ -3,7 +3,7 @@ import { MouseEvent } from 'react' import { DOCUMENTATION_HOME_PAGE } from '@Common/Constants' import { Button, ButtonComponentType, ButtonVariantType, Icon } from '@Shared/Components' import { ComponentSizeType } from '@Shared/constants' -import { useMainContext } from '@Shared/Providers' +import { SidePanelTab, useMainContext } from '@Shared/Providers' import { DocLinkProps } from './types' import { getDocumentationUrl } from './utils' @@ -34,7 +34,7 @@ export const DocLink = ({ const handleClick = (e: MouseEvent) => { if (!isExternalLink && !openInNewTab && !e.metaKey && documentationLink.startsWith(DOCUMENTATION_HOME_PAGE)) { e.preventDefault() - setSidePanelConfig((prev) => ({ ...prev, open: true, docLink: documentationLink })) + setSidePanelConfig((prev) => ({ ...prev, state: SidePanelTab.DOCUMENTATION, docLink: documentationLink })) } onClick?.(e) } diff --git a/src/Shared/Components/Header/IframePromoButton.tsx b/src/Shared/Components/Header/IframePromoButton.tsx index 85545d044..41bb72805 100644 --- a/src/Shared/Components/Header/IframePromoButton.tsx +++ b/src/Shared/Components/Header/IframePromoButton.tsx @@ -88,7 +88,7 @@ export const IframePromoButton = () => { ) return ( -
+ <> {FEATURE_PROMO_EMBEDDED_BUTTON_TEXT && (
+ ) } diff --git a/src/Shared/Components/Header/PageHeader.tsx b/src/Shared/Components/Header/PageHeader.tsx index 858b9933d..2c48cc646 100644 --- a/src/Shared/Components/Header/PageHeader.tsx +++ b/src/Shared/Components/Header/PageHeader.tsx @@ -21,12 +21,15 @@ import Tippy from '@tippyjs/react' import { ReactComponent as ICCaretDownSmall } from '@Icons/ic-caret-down-small.svg' import { ReactComponent as Close } from '@Icons/ic-close.svg' import { ReactComponent as ICMediumPaintBucket } from '@IconsV2/ic-medium-paintbucket.svg' +import { ComponentSizeType } from '@Shared/constants' import { InstallationType } from '@Shared/types' import { getAlphabetIcon, TippyCustomized, TippyTheme } from '../../../Common' import { MAX_LOGIN_COUNT, POSTHOG_EVENT_ONBOARDING } from '../../../Common/Constants' -import { useMainContext, useTheme, useUserEmail } from '../../Providers' +import { SidePanelTab, useMainContext, useTheme, useUserEmail } from '../../Providers' +import { Button, ButtonStyleType, ButtonVariantType } from '../Button' import GettingStartedCard from '../GettingStartedCard/GettingStarted' +import { Icon } from '../Icon' import { InfoIconTippy } from '../InfoIconTippy' import LogoutCard from '../LogoutCard' import { HelpButton } from './HelpButton' @@ -50,8 +53,14 @@ const PageHeader = ({ markAsBeta, tippyProps, }: PageHeaderType) => { - const { loginCount, setLoginCount, showGettingStartedCard, setShowGettingStartedCard, licenseData } = - useMainContext() + const { + loginCount, + setLoginCount, + showGettingStartedCard, + setShowGettingStartedCard, + licenseData, + setSidePanelConfig, + } = useMainContext() const { showSwitchThemeLocationTippy, handleShowSwitchThemeLocationTippyChange } = useTheme() const { isTippyCustomized, tippyRedirectLink, TippyIcon, tippyMessage, onClickTippyButton, additionalContent } = @@ -133,8 +142,21 @@ const PageHeader = ({ ) + const onAskButtonClick = () => { + setSidePanelConfig(() => ({ state: SidePanelTab.ASK_DEVTRON })) + } + const renderLogoutHelpSection = () => ( <> + )} Date: Tue, 10 Jun 2025 13:51:45 +0530 Subject: [PATCH 09/11] fix: review comments --- .../DraggableWrapper/DraggableButton.tsx | 4 ++-- src/Common/DraggableWrapper/types.ts | 1 - .../Typewriter/Typewriter.component.tsx | 20 ------------------- src/Shared/Components/Typewriter/index.ts | 1 - 4 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 src/Shared/Components/Typewriter/Typewriter.component.tsx diff --git a/src/Common/DraggableWrapper/DraggableButton.tsx b/src/Common/DraggableWrapper/DraggableButton.tsx index cb949d618..48349887e 100644 --- a/src/Common/DraggableWrapper/DraggableButton.tsx +++ b/src/Common/DraggableWrapper/DraggableButton.tsx @@ -18,13 +18,13 @@ import React from 'react' import { ReactComponent as ICDrag } from '../../Assets/Icon/ic-drag.svg' import { DraggableButtonProps } from './types' -export default function DraggableButton({ dragClassName, svgClassName = 'fcn-6' }: DraggableButtonProps) { +export default function DraggableButton({ dragClassName }: DraggableButtonProps) { return ( ) } diff --git a/src/Common/DraggableWrapper/types.ts b/src/Common/DraggableWrapper/types.ts index ccbf3b320..91b9f5f82 100644 --- a/src/Common/DraggableWrapper/types.ts +++ b/src/Common/DraggableWrapper/types.ts @@ -49,5 +49,4 @@ export interface DraggableWrapperProps { */ export interface DraggableButtonProps { dragClassName: string - svgClassName?: string } diff --git a/src/Shared/Components/Typewriter/Typewriter.component.tsx b/src/Shared/Components/Typewriter/Typewriter.component.tsx deleted file mode 100644 index 95c8513d1..000000000 --- a/src/Shared/Components/Typewriter/Typewriter.component.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { motion } from 'framer-motion' - -import { BlinkingCursor } from './BlinkingCursor' -import { useTypewriter } from './useTypewriter' - -interface TypewriterProps { - text: string -} - -export const Typewriter = ({ text }: TypewriterProps) => { - const visibleText = useTypewriter(text) - - return ( - - {visibleText} - - - - ) -} diff --git a/src/Shared/Components/Typewriter/index.ts b/src/Shared/Components/Typewriter/index.ts index 74c5f637e..441759395 100644 --- a/src/Shared/Components/Typewriter/index.ts +++ b/src/Shared/Components/Typewriter/index.ts @@ -1,3 +1,2 @@ export { BlinkingCursor } from './BlinkingCursor' -export { Typewriter } from './Typewriter.component' export { useTypewriter } from './useTypewriter' From 9fbfea95d4d13f7e49f201b3fa170ddc014d1aa5 Mon Sep 17 00:00:00 2001 From: Amrit Kashyap Borah Date: Tue, 10 Jun 2025 14:08:24 +0530 Subject: [PATCH 10/11] fix: use forwardRef instead in Button Component --- .../Components/Button/Button.component.tsx | 359 +++++++++--------- src/Shared/Components/Button/types.ts | 3 +- 2 files changed, 184 insertions(+), 178 deletions(-) diff --git a/src/Shared/Components/Button/Button.component.tsx b/src/Shared/Components/Button/Button.component.tsx index 2ce4d1d59..23e7d9e33 100644 --- a/src/Shared/Components/Button/Button.component.tsx +++ b/src/Shared/Components/Button/Button.component.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { MutableRefObject, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react' +import { forwardRef, MutableRefObject, PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react' import { Link } from 'react-router-dom' import { Progressing } from '@Common/Progressing' @@ -26,95 +26,96 @@ import { getButtonDerivedClass, getButtonIconClassName, getButtonLoaderSize } fr import './button.scss' -const ButtonElement = ({ - component = ButtonComponentType.button, - anchorProps, - linkProps, - buttonProps, - onClick, - elementRef, - buttonRef, - ...props -}: PropsWithChildren< - Omit< - ButtonProps, - | 'text' - | 'variant' - | 'size' - | 'style' - | 'startIcon' - | 'endIcon' - | 'showTooltip' - | 'tooltipProps' - | 'dataTestId' - | 'isLoading' - | 'ariaLabel' - | 'showAriaLabelInTippy' - > & { - className: string - 'data-testid': ButtonProps['dataTestId'] - 'aria-label': ButtonProps['ariaLabel'] - elementRef: MutableRefObject - } ->) => { - // Added the specific class to ensure that the link override is applied - const linkOrAnchorClassName = `${props.className} button__link ${props.disabled ? 'dc__disable-click' : ''}` - - // NOTE: If the ref callback is re-created every render (i.e., not wrapped in useCallback), - // it will be invoked on every render: first with null, then with the new node. - const refCallback = useCallback((el: HTMLButtonElement | HTMLAnchorElement) => { - if (!el) { - return +const ButtonElement = forwardRef< + HTMLButtonElement | HTMLAnchorElement, + PropsWithChildren< + Omit< + ButtonProps, + | 'text' + | 'variant' + | 'size' + | 'style' + | 'startIcon' + | 'endIcon' + | 'showTooltip' + | 'tooltipProps' + | 'dataTestId' + | 'isLoading' + | 'ariaLabel' + | 'showAriaLabelInTippy' + > & { + className: string + 'data-testid': ButtonProps['dataTestId'] + 'aria-label': ButtonProps['ariaLabel'] + elementRef: MutableRefObject } + > +>( + ( + { component = ButtonComponentType.button, anchorProps, linkProps, buttonProps, onClick, elementRef, ...props }, + forwardedRef, + ) => { + // Added the specific class to ensure that the link override is applied + const linkOrAnchorClassName = `${props.className} button__link ${props.disabled ? 'dc__disable-click' : ''}` - // eslint-disable-next-line no-param-reassign - elementRef.current = el + // NOTE: If the ref callback is re-created every render (i.e., not wrapped in useCallback), + // it will be invoked on every render: first with null, then with the new node. + const refCallback = useCallback((el: HTMLButtonElement | HTMLAnchorElement) => { + if (!el) { + return + } - if (buttonRef && typeof buttonRef === 'object' && Object.hasOwn(buttonRef, 'current')) { // eslint-disable-next-line no-param-reassign - buttonRef.current = el + elementRef.current = el + + if (forwardedRef && typeof forwardedRef === 'object' && Object.hasOwn(forwardedRef, 'current')) { + // eslint-disable-next-line no-param-reassign + forwardedRef.current = el + } else if (typeof forwardedRef === 'function') { + forwardedRef(el) + } + }, []) + + if (component === ButtonComponentType.link) { + return ( + ['onClick']} + ref={refCallback} + /> + ) } - }, []) - if (component === ButtonComponentType.link) { - return ( - ['onClick']} - ref={refCallback} - /> - ) - } + if (component === ButtonComponentType.anchor) { + return ( + ['onClick']} + ref={refCallback} + > + {props.children} + + ) + } - if (component === ButtonComponentType.anchor) { return ( - ['onClick']} ref={refCallback} - > - {props.children} - + /> ) - } - - return ( -