Skip to content
49 changes: 34 additions & 15 deletions components/form/prompt-input/PromptInput.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,44 @@ import PropTypes from 'prop-types'
import classnames from 'classnames'

function PromptInput ({ className, children }) {

let textarea = null
let actions = null
let overlay = null
const otherChildren = []

const identifyChildComponent = (child) => {
if (child && child.type) {
if (child.type.displayName === 'PromptInputTextarea') {
return 'textarea'
} else if (child.type.displayName === 'PromptInputActions') {
return 'actions'
} else if (child.type.name === 'TextRevealOverlay') {
return 'overlay'
} else {
return 'other'
}
}
return null
}

React.Children.forEach(children, child => {
if (child.type && child.type.displayName === 'PromptInputTextarea') {
textarea = child
} else if (child.type && child.type.displayName === 'PromptInputActions') {
actions = child
} else if (child.type && child.type.name === 'TextRevealOverlay') {
overlay = child
const componentType = identifyChildComponent(child)

switch (componentType) {
case 'textarea':
textarea = child
break
case 'actions':
actions = child
break
case 'overlay':
overlay = child
break
case 'other':
otherChildren.push(child)
break
default:
break
}
})

Expand All @@ -23,14 +49,7 @@ function PromptInput ({ className, children }) {
{textarea}
{overlay}
{actions}
{React.Children.map(children, child => {
if ((child.type && child.type.displayName !== 'PromptInputTextarea' &&
child.type.displayName !== 'PromptInputActions') &&
(child.type && child.type.name !== 'TextRevealOverlay')) {
return child
}
return null
})}
{otherChildren}
</div>
)
}
Expand Down
23 changes: 16 additions & 7 deletions components/form/prompt-input/TextRevealOverlay.es6.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'

function TextRevealOverlay({
text,
Expand All @@ -25,6 +26,7 @@ function TextRevealOverlay({
}
}, [text])

// Handle animation completion when the last paragraph finishes animating
useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some short comments to all useEffect hooks in this component?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be good to move all those useEffects to a separate hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments to the useEffect but did not move into a separate hook since it's only being used by this component.

if (isAnimatingLastParagraph && lastParagraphIndex === paragraphs.length - 1) {
const timer = setTimeout(() => {
Expand All @@ -38,14 +40,14 @@ function TextRevealOverlay({
}
}, [isAnimatingLastParagraph, lastParagraphIndex, paragraphs.length, onAnimationComplete])


// Detect when the last paragraph appears and mark it for animation
useEffect(() => {
if (lastParagraphIndex === paragraphs.length - 1 && paragraphs.length > 0) {
setIsAnimatingLastParagraph(true)
}
}, [lastParagraphIndex, paragraphs.length])


// Sync overlay dimensions, positioning and scrolling with the textarea
useEffect(() => {
if (!textareaRef || !textareaRef.current || !overlayRef.current || !isRevealing) return

Expand Down Expand Up @@ -84,7 +86,8 @@ function TextRevealOverlay({
overlay.style.position = 'absolute'
overlay.style.top = paddingTop
overlay.style.left = paddingLeft
overlay.style.width = 'calc(100% - ' + (parseInt(paddingLeft) * 2) + 'px)'
overlay.style.width = `calc(100% - ${parseInt(paddingLeft) * 2}px)`


// Set max-height to match textarea max-height
overlay.style.maxHeight = textareaStyles.maxHeight
Expand Down Expand Up @@ -143,15 +146,21 @@ function TextRevealOverlay({
return (
<div
ref={overlayRef}
className={`ds-prompt-input__text-reveal-overlay ${className || ''}`}
className={classnames(
'ds-prompt-input__text-reveal-overlay',
className
)}
aria-hidden="true"
>
{paragraphs.map((paragraph, index) => (
<p
key={index}
className={`ds-prompt-input__text-reveal-paragraph ${
index === lastParagraphIndex ? 'ds-prompt-input__text-reveal-paragraph--animating' : ''
}`}
className={classnames(
'ds-prompt-input__text-reveal-paragraph',
{
'ds-prompt-input__text-reveal-paragraph--animating': index === lastParagraphIndex
}
)}
>
{paragraph}
</p>
Expand Down
6 changes: 6 additions & 0 deletions src/react-demo/PromptInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,25 +171,29 @@ export default function PromptInputPage () {
}
]

// Update message when typing animation generates new text
useEffect(() => {
if (isTypingPrompt && typedText) {
setMessage(typedText)
}
}, [isTypingPrompt, typedText])

// Enable send button when typing animation completes
useEffect(() => {
if (!isTypingPrompt) {
setCanTriggerSend(true)
}
}, [message, isTypingPrompt])

// Reset recording state when typing from recording completes
useEffect(() => {
if (isRecording && typingStatus === TypingStatus.COMPLETED && isTypingFromRecording) {
setIsRecording(false)
setIsTypingFromRecording(false)
}
}, [isRecording, typingStatus, isTypingFromRecording])

// Cleanup timeouts when component unmounts
useEffect(() => {
return () => {
if (submitTimerRef.current) {
Expand All @@ -209,6 +213,7 @@ export default function PromptInputPage () {
}
}, [])

// Update textarea with revealed text during paragraph animation
useEffect(() => {
if (currentText) {
// Update message with current revealed text
Expand All @@ -217,6 +222,7 @@ export default function PromptInputPage () {
}
}, [currentText, resizeTextarea])

// Reset animation state when text reveal completes
useEffect(() => {
if (isTypingFromRecording) return
if (!isRevealing && textareaRef.current) {
Expand Down
Loading