({
documentationLink.startsWith(DOCUMENTATION_HOME_PAGE)
) {
e.preventDefault()
- setSidePanelConfig((prev) => ({ ...prev, open: true, docLink: documentationLink, reinitialize: true }))
+ setSidePanelConfig((prev) => ({
+ ...prev,
+ state: SidePanelTab.DOCUMENTATION,
+ docLink: documentationLink,
+ reinitialize: true,
+ }))
}
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 28ef087ce..17bd34f69 100644
--- a/src/Shared/Components/Header/PageHeader.tsx
+++ b/src/Shared/Components/Header/PageHeader.tsx
@@ -24,8 +24,9 @@ import { InstallationType } from '@Shared/types'
import { 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 GettingStartedCard from '../GettingStartedCard/GettingStarted'
+import { Icon } from '../Icon'
import { InfoIconTippy } from '../InfoIconTippy'
import { HelpButton } from './HelpButton'
import { IframePromoButton } from './IframePromoButton'
@@ -49,7 +50,14 @@ const PageHeader = ({
markAsBeta,
tippyProps,
}: PageHeaderType) => {
- const { loginCount, setLoginCount, showGettingStartedCard, setShowGettingStartedCard } = useMainContext()
+ const {
+ loginCount,
+ setLoginCount,
+ showGettingStartedCard,
+ setShowGettingStartedCard,
+ setSidePanelConfig,
+ sidePanelConfig,
+ } = useMainContext()
const { showSwitchThemeLocationTippy, handleShowSwitchThemeLocationTippyChange } = useTheme()
const { isTippyCustomized, tippyRedirectLink, TippyIcon, tippyMessage, onClickTippyButton, additionalContent } =
@@ -124,8 +132,22 @@ const PageHeader = ({
)
+ const onAskButtonClick = () => {
+ setSidePanelConfig((prev) => ({ ...prev, state: SidePanelTab.ASK_DEVTRON }))
+ }
+
const renderLogoutHelpSection = () => (
<>
+ {window._env_?.FEATURE_ASK_DEVTRON_EXPERT && sidePanelConfig.state === 'closed' && (
+
+ )}
{
const textareaRef = useRef(null)
@@ -58,6 +59,8 @@ const Textarea = ({
// else, it behaves as controlled
const [text, setText] = useState('')
+ const { MIN_HEIGHT, AUTO_EXPANSION_MAX_HEIGHT } = getTextAreaConstraintsForSize(size)
+
const updateRefsHeight = (height: number) => {
const refElement = textareaRef.current
if (refElement) {
@@ -65,6 +68,19 @@ const Textarea = ({
}
}
+ const refCallback = useCallback((node: HTMLTextAreaElement) => {
+ if (textareaRefProp) {
+ if (typeof textareaRefProp === 'function') {
+ textareaRefProp(node)
+ } else {
+ // eslint-disable-next-line no-param-reassign
+ textareaRefProp.current = node
+ }
+ }
+
+ textareaRef.current = node
+ }, [])
+
const reInitHeight = () => {
const currentHeight = parseInt(textareaRef.current.style.height, 10)
let nextHeight = textareaRef.current.scrollHeight || 0
@@ -119,6 +135,10 @@ const Textarea = ({
if (event.key === 'Enter' || event.key === 'Escape') {
event.stopPropagation()
+ if (newlineOnShiftEnter && event.key === 'Enter' && !event.shiftKey) {
+ event.preventDefault()
+ }
+
if (event.key === 'Escape') {
textareaRef.current.blur()
}
@@ -163,11 +183,12 @@ const Textarea = ({
onBlur={handleBlur}
onKeyDown={handleKeyDown}
className={`${COMPONENT_SIZE_TYPE_TO_FONT_AND_BLOCK_PADDING_MAP[size]} ${COMPONENT_SIZE_TYPE_TO_INLINE_PADDING_MAP[size]} ${deriveBorderRadiusAndBorderClassFromConfig({ borderConfig, borderRadiusConfig })} w-100 dc__overflow-auto textarea`}
- ref={textareaRef}
+ ref={refCallback}
style={{
// No max height when user is expanding
maxHeight: 'none',
minHeight: MIN_HEIGHT,
+ resize: disableResize ? 'none' : 'vertical',
}}
/>
diff --git a/src/Shared/Components/Textarea/types.ts b/src/Shared/Components/Textarea/types.ts
index 4584dfdd6..2fb12cd87 100644
--- a/src/Shared/Components/Textarea/types.ts
+++ b/src/Shared/Components/Textarea/types.ts
@@ -35,9 +35,25 @@ export interface TextareaProps
*
* @default ComponentSizeType.large
*/
- size?: Extract
+ size?: Extract
/**
* Value of the textarea
*/
value: string
+ /**
+ * If true, the textarea resize is disabled
+ *
+ * @default false
+ */
+ disableResize?: true
+ /**
+ * Allows inserting a newline with Shift + Enter instead of Enter alone.
+ *
+ * When enabled, pressing Enter submits the form, while Shift + Enter inserts a newline.
+ * Useful for forms where Enter should trigger submission, but multiline input is still needed.
+ *
+ * @default false
+ */
+ newlineOnShiftEnter?: boolean
+ textareaRef?: React.MutableRefObject | React.RefCallback
}
diff --git a/src/Shared/Components/Textarea/utils.ts b/src/Shared/Components/Textarea/utils.ts
new file mode 100644
index 000000000..a9cfe4699
--- /dev/null
+++ b/src/Shared/Components/Textarea/utils.ts
@@ -0,0 +1,15 @@
+import { ComponentSizeType } from '@Shared/constants'
+
+import { TEXTAREA_CONSTRAINTS } from './constants'
+import { TextareaProps } from './types'
+
+export const getTextAreaConstraintsForSize = (size: TextareaProps['size']) => {
+ if (size === ComponentSizeType.small) {
+ return {
+ MIN_HEIGHT: 56,
+ AUTO_EXPANSION_MAX_HEIGHT: 150,
+ }
+ }
+
+ return TEXTAREA_CONSTRAINTS
+}
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/index.ts b/src/Shared/Components/Typewriter/index.ts
new file mode 100644
index 000000000..441759395
--- /dev/null
+++ b/src/Shared/Components/Typewriter/index.ts
@@ -0,0 +1,2 @@
+export { BlinkingCursor } from './BlinkingCursor'
+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 fceeef3bd..fa263a080 100644
--- a/src/Shared/Components/index.ts
+++ b/src/Shared/Components/index.ts
@@ -97,6 +97,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'
diff --git a/src/Shared/Providers/index.ts b/src/Shared/Providers/index.ts
index a1dcc842c..8ad4a382f 100644
--- a/src/Shared/Providers/index.ts
+++ b/src/Shared/Providers/index.ts
@@ -18,4 +18,5 @@ export * from './ImageSelectionUtility'
export * from './MainContextProvider'
export * from './ThemeProvider'
export type { MainContext, ReloadVersionConfigTypes, SidePanelConfig } from './types'
+export { SidePanelTab } from './types'
export * from './UserEmailProvider'
diff --git a/src/Shared/Providers/types.ts b/src/Shared/Providers/types.ts
index a21a45e9e..01ceaada2 100644
--- a/src/Shared/Providers/types.ts
+++ b/src/Shared/Providers/types.ts
@@ -30,9 +30,13 @@ export interface ReloadVersionConfigTypes {
isRefreshing: boolean
}
+export enum SidePanelTab {
+ DOCUMENTATION = 'documentation',
+ ASK_DEVTRON = 'ask-devtron',
+}
+
export interface SidePanelConfig {
- /** Determines whether the side panel is visible */
- open: boolean
+ state: SidePanelTab | 'closed'
/** Optional flag to reset/reinitialize the side panel state */
reinitialize?: boolean
/** URL to documentation that should be displayed in the panel */
@@ -79,6 +83,13 @@ type CommonMainContextProps = {
setLicenseData: Dispatch>
canFetchHelmAppStatus: boolean
setIntelligenceConfig: Dispatch>
+ aiAgentContext: {
+ path: string
+ context: Record
+ }
+ setAIAgentContext: (aiAgentContext: CommonMainContextProps['aiAgentContext']) => void
+
+ sidePanelConfig: SidePanelConfig
setSidePanelConfig: Dispatch>
}
diff --git a/src/index.ts b/src/index.ts
index 1826cf696..4ad98de18 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -159,6 +159,7 @@ export interface customEnv {
GATEKEEPER_URL?: string
FEATURE_AI_INTEGRATION_ENABLE?: boolean
LOGIN_PAGE_IMAGE?: string
+ FEATURE_ASK_DEVTRON_EXPERT?: boolean
/**
* If true, the manage traffic feature is enabled in apps & app groups.
*