diff --git a/packages/@react-spectrum/ai/exports/index.ts b/packages/@react-spectrum/ai/exports/index.ts index 7fd5afd63de..fb1a02b611f 100644 --- a/packages/@react-spectrum/ai/exports/index.ts +++ b/packages/@react-spectrum/ai/exports/index.ts @@ -49,6 +49,7 @@ export type { PromptFieldToolbarProps, PromptFieldTokenValue, InsertMenuItemProps, + AttachFileMenuItemProps, PromptFieldVoiceButtonProps, InsertTokenMenuItemProps, InsertTextMenuItemProps, diff --git a/packages/@react-spectrum/ai/src/PromptField.tsx b/packages/@react-spectrum/ai/src/PromptField.tsx index 83958e2f52a..554fed32940 100644 --- a/packages/@react-spectrum/ai/src/PromptField.tsx +++ b/packages/@react-spectrum/ai/src/PromptField.tsx @@ -33,7 +33,7 @@ import { } from 'react'; import {FocusableRef} from '@react-types/shared'; import {getInteractionModality} from 'react-aria/private/interactions/useFocusVisible'; -import {IconContext} from '@react-spectrum/s2'; +import {IconContext, MenuTriggerProps} from '@react-spectrum/s2'; import {Image, Text} from '@react-spectrum/s2/Card'; // @ts-ignore import intlMessages from '../intl/*.json'; @@ -941,15 +941,17 @@ function buildVoicePrompt(base: TokenFieldValue, voiceText: string): PromptField return base.replaceRange(base.caretPosition, base.caretPosition, voiceText) as PromptFieldValue; } -export interface InsertMenuItemProps { +// TODO: how do we feed about these extending menutrigger props? IMO the "InsertMenuButton" makes +// it a bit weird to pass MenuTrigger props to it +export interface InsertMenuItemProps extends Pick { children: React.ReactNode; } export function InsertMenuButton(props: InsertMenuItemProps) { - let {children} = props; + let {children, onOpenChange} = props; let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/ai'); return ( - + ); } +export interface AttachFileMenuItemProps extends Omit< + MenuItemProps, + | 'children' + | 'UNSAFE_className' + | 'UNSAFE_style' + | 'download' + | 'href' + | 'hrefLang' + | 'ping' + | 'referrerPolicy' + | 'rel' + | 'routerOptions' + | 'target' +> {} -export function AttachFileMenuItem() { +export function AttachFileMenuItem(props: AttachFileMenuItemProps) { + let {onAction, ...otherProps} = props; let {acceptedAttachmentTypes, setAttachments, onAddAttachments} = useContext(PromptFieldContext); return ( { + onAction?.(); let input = document.createElement('input'); input.type = 'file'; if (acceptedAttachmentTypes) { diff --git a/packages/@react-spectrum/ai/test/PromptField.test.tsx b/packages/@react-spectrum/ai/test/PromptField.test.tsx index 6729af91ab9..cd21c38c4ab 100644 --- a/packages/@react-spectrum/ai/test/PromptField.test.tsx +++ b/packages/@react-spectrum/ai/test/PromptField.test.tsx @@ -11,6 +11,13 @@ */ import {act, screen, waitFor} from '@react-spectrum/test-utils-internal'; +import { + AttachFileMenuItem, + InsertMenuButton, + PromptField, + PromptFieldToolbar, + PromptTokenField +} from '../src/PromptField'; import { imageAttachment, installRangePolyfill, @@ -18,7 +25,6 @@ import { renderPromptField, tokenTexts } from './utils/promptFieldTestUtils'; -import {PromptField, PromptTokenField} from '../src/PromptField'; import React from 'react'; import {render} from '@react-spectrum/test-utils-internal'; import userEvent from '@testing-library/user-event'; @@ -444,6 +450,41 @@ describeOrSkip('PromptField', () => { }); }); + describe('InsertMenuButton', () => { + it('calls onOpenChange when the menu is opened', async () => { + let onOpenChange = jest.fn(); + let {getByRole} = render( + + + + + + + + + ); + await user.click(getByRole('button', {name: 'Add'})); + expect(onOpenChange).toHaveBeenCalledWith(true); + }); + + it('calls AttachFileMenuItem onAction when the item is selected', async () => { + let onAction = jest.fn(); + let {getByRole} = render( + + + + + + + + + ); + await user.click(getByRole('button', {name: 'Add'})); + await user.click(await findMenuItem('Attach a file')); + expect(onAction).toHaveBeenCalled(); + }); + }); + it('fires onKeyDown when a key is pressed in the token field', async () => { let onKeyDown = jest.fn(); let {getByRole} = render(