-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(opentrons-ai-client) add input textbox to container (#14968)
* feat(opentrons-ai-client) add input textbox to container
- Loading branch information
1 parent
2c8f7cd
commit d020ea8
Showing
9 changed files
with
227 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React from 'react' | ||
import { describe, it, expect } from 'vitest' | ||
import { fireEvent, screen } from '@testing-library/react' | ||
import { renderWithProviders } from '../../../__testing-utils__' | ||
import { i18n } from '../../../i18n' | ||
import { InputPrompt } from '../index' | ||
|
||
const render = () => { | ||
return renderWithProviders(<InputPrompt />, { i18nInstance: i18n }) | ||
} | ||
|
||
describe('InputPrompt', () => { | ||
it('should render textarea and disabled button', () => { | ||
render() | ||
screen.getByRole('textbox') | ||
screen.queryByPlaceholderText('Type your prompt...') | ||
screen.getByRole('button') | ||
expect(screen.getByRole('button')).toBeDisabled() | ||
}) | ||
|
||
it('should make send button not disabled when a user inputs something in textarea', () => { | ||
render() | ||
const textbox = screen.getByRole('textbox') | ||
fireEvent.change(textbox, { target: { value: ['test'] } }) | ||
expect(screen.getByRole('button')).not.toBeDisabled() | ||
}) | ||
|
||
// ToDo (kk:04/19/2024) add more test cases | ||
}) |
149 changes: 149 additions & 0 deletions
149
opentrons-ai-client/src/molecules/InputPrompt/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import React from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import styled, { css } from 'styled-components' | ||
import { useForm } from 'react-hook-form' | ||
|
||
import { | ||
ALIGN_CENTER, | ||
BORDERS, | ||
Btn, | ||
COLORS, | ||
DIRECTION_ROW, | ||
DISPLAY_FLEX, | ||
Flex, | ||
Icon, | ||
JUSTIFY_CENTER, | ||
SPACING, | ||
TYPOGRAPHY, | ||
} from '@opentrons/components' | ||
|
||
import type { SubmitHandler } from 'react-hook-form' | ||
|
||
// ToDo (kk:04/19/2024) Note this interface will be used by prompt buttons in SidePanel | ||
// interface InputPromptProps {} | ||
|
||
interface InputType { | ||
userPrompt: string | ||
} | ||
|
||
export function InputPrompt(/* props: InputPromptProps */): JSX.Element { | ||
const { t } = useTranslation('protocol_generator') | ||
const { register, handleSubmit, watch } = useForm<InputType>({ | ||
defaultValues: { | ||
userPrompt: '', | ||
}, | ||
}) | ||
const userPrompt = watch('userPrompt') ?? '' | ||
|
||
const onSubmit: SubmitHandler<InputType> = async data => { | ||
// ToDo (kk: 04/19/2024) call api | ||
const { userPrompt } = data | ||
console.log('user prompt', userPrompt) | ||
} | ||
|
||
return ( | ||
<StyledForm id="User_Prompt" onSubmit={() => handleSubmit(onSubmit)}> | ||
<Flex | ||
padding={SPACING.spacing40} | ||
gridGap={SPACING.spacing40} | ||
flexDirection={DIRECTION_ROW} | ||
backgroundColor={COLORS.white} | ||
borderRadius={BORDERS.borderRadius4} | ||
justifyContent={JUSTIFY_CENTER} | ||
alignItems={ALIGN_CENTER} | ||
> | ||
<StyledTextarea | ||
rows={1} | ||
placeholder={t('type_your_prompt')} | ||
{...register('userPrompt')} | ||
/> | ||
<PlayButton disabled={userPrompt.length === 0} /> | ||
</Flex> | ||
</StyledForm> | ||
) | ||
} | ||
|
||
const StyledForm = styled.form` | ||
width: 100%; | ||
` | ||
|
||
const StyledTextarea = styled.textarea` | ||
resize: none; | ||
min-height: 3.75rem; | ||
background-color: ${COLORS.white}; | ||
border: none; | ||
outline: none; | ||
padding: 0; | ||
box-shadow: none; | ||
color: ${COLORS.black90}; | ||
width: 100%; | ||
font-size: ${TYPOGRAPHY.fontSize20}; | ||
line-height: ${TYPOGRAPHY.lineHeight24}; | ||
::placeholder { | ||
position: absolute; | ||
top: 50%; | ||
transform: translateY(-50%); | ||
} | ||
` | ||
|
||
interface PlayButtonProps { | ||
onPlay?: () => void | ||
disabled?: boolean | ||
isLoading?: boolean | ||
} | ||
|
||
function PlayButton({ | ||
onPlay, | ||
disabled = false, | ||
isLoading = false, | ||
}: PlayButtonProps): JSX.Element { | ||
const playButtonStyle = css` | ||
-webkit-tap-highlight-color: transparent; | ||
&:focus { | ||
background-color: ${COLORS.blue60}; | ||
color: ${COLORS.white}; | ||
} | ||
&:hover { | ||
background-color: ${COLORS.blue50}; | ||
color: ${COLORS.white}; | ||
} | ||
&:focus-visible { | ||
background-color: ${COLORS.blue50}; | ||
} | ||
&:active { | ||
background-color: ${COLORS.blue60}; | ||
color: ${COLORS.white}; | ||
} | ||
&:disabled { | ||
background-color: ${COLORS.grey35}; | ||
color: ${COLORS.grey50}; | ||
} | ||
` | ||
return ( | ||
<Btn | ||
alignItems={ALIGN_CENTER} | ||
backgroundColor={disabled ? COLORS.grey35 : COLORS.blue50} | ||
borderRadius={BORDERS.borderRadiusFull} | ||
display={DISPLAY_FLEX} | ||
justifyContent={JUSTIFY_CENTER} | ||
width="4.25rem" | ||
height="3.75rem" | ||
disabled={disabled || isLoading} | ||
onClick={onPlay} | ||
aria-label="play" | ||
css={playButtonStyle} | ||
type="submit" | ||
> | ||
<Icon | ||
color={disabled ? COLORS.grey50 : COLORS.white} | ||
name={isLoading ? 'ot-spinner' : 'send'} | ||
spin={isLoading} | ||
size="2rem" | ||
/> | ||
</Btn> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,64 @@ | ||
import React from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import { css } from 'styled-components' | ||
import { | ||
COLORS, | ||
DIRECTION_COLUMN, | ||
FLEX_MAX_CONTENT, | ||
Flex, | ||
POSITION_ABSOLUTE, | ||
POSITION_RELATIVE, | ||
SPACING, | ||
StyledText, | ||
TYPOGRAPHY, | ||
} from '@opentrons/components' | ||
import { PromptGuide } from '../../molecules/PromptGuide' | ||
import { InputPrompt } from '../../molecules/InputPrompt' | ||
|
||
export function ChatContainer(): JSX.Element { | ||
const { t } = useTranslation('protocol_generator') | ||
const isDummyInitial = true | ||
return ( | ||
<Flex | ||
padding={SPACING.spacing40} | ||
padding={`${SPACING.spacing40} ${SPACING.spacing40} ${SPACING.spacing24}`} | ||
backgroundColor={COLORS.grey10} | ||
width={FLEX_MAX_CONTENT} | ||
width="100%" | ||
> | ||
{/* This will be updated when input textbox and function are implemented */} | ||
{isDummyInitial ? ( | ||
<Flex | ||
flexDirection={DIRECTION_COLUMN} | ||
gridGap={SPACING.spacing12} | ||
position={POSITION_RELATIVE} | ||
width="100%" | ||
> | ||
<StyledText>{t('opentronsai')}</StyledText> | ||
<PromptGuide /> | ||
<Flex | ||
flexDirection={DIRECTION_COLUMN} | ||
gridGap={SPACING.spacing12} | ||
width="100%" | ||
> | ||
<StyledText>{t('opentronsai')}</StyledText> | ||
<PromptGuide /> | ||
</Flex> | ||
<Flex | ||
position={POSITION_ABSOLUTE} | ||
bottom="0" | ||
width="100%" | ||
gridGap={SPACING.spacing24} | ||
flexDirection={DIRECTION_COLUMN} | ||
> | ||
<InputPrompt /> | ||
<StyledText css={DISCLAIMER_TEXT_STYLE}> | ||
{t('disclaimer')} | ||
</StyledText> | ||
</Flex> | ||
</Flex> | ||
) : null} | ||
</Flex> | ||
) | ||
} | ||
|
||
const DISCLAIMER_TEXT_STYLE = css` | ||
color: ${COLORS.grey55}; | ||
font-size: ${TYPOGRAPHY.fontSize20}; | ||
line-height: ${TYPOGRAPHY.lineHeight24}; | ||
text-align: ${TYPOGRAPHY.textAlignCenter}; | ||
` |