Skip to content
4 changes: 2 additions & 2 deletions packages/@react-spectrum/ai/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export {
} from '../src/PromptField';
export {ResponseStatus, ResponseStatusTitle, ResponseStatusPanel} from '../src/ResponseStatus';
export {Chat, Thread, ThreadItem, ThreadScrollButton} from '../src/Chat';
export {TokenSegmentList} from '../src/TokenSegmentList';
export {TokenSegmentList} from 'react-aria-components/TokenField';
export {UserMessage} from '../src/UserMessage';

export type {AttachmentProps, AttachmentListProps} from '../src/AttachmentList';
Expand All @@ -39,5 +39,5 @@ export type {
ResponseStatusPanelProps
} from '../src/ResponseStatus';
export type {ChatProps, ThreadProps, ThreadItemProps, ThreadScrollButtonProps} from '../src/Chat';
export type {TokenSegmentListOptions} from '../src/TokenSegmentList';
export type {TokenSegmentListOptions} from 'react-aria-components/TokenField';
export type {UserMessageProps} from '../src/UserMessage';
57 changes: 32 additions & 25 deletions packages/@react-spectrum/ai/src/PromptField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,13 @@ import {
useRef,
useState
} from 'react';
// eslint-disable-next-line
import {
Direction,
Position,
TokenFieldSegment,
TokenSegment,
TokenSegmentList
} from './TokenSegmentList';
} from 'react-stately/useTokenFieldState';
import {getEventTarget} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {Group} from 'react-aria-components/Group';
import {IconContext, mergeStyles} from '@react-spectrum/s2';
Expand All @@ -43,10 +42,15 @@ import {isFileDropItem, useDrop} from 'react-aria-components/useDrop';
import {isFocusable} from 'react-aria/private/utils/isFocusable';
import {Link} from '@react-spectrum/s2/Link';
import {Menu, MenuItem, MenuItemProps, MenuTrigger} from '@react-spectrum/s2/Menu';
// eslint-disable-next-line
import Plus from '@react-spectrum/s2/icons/Add';
import {Popover, PopoverProps} from '@react-spectrum/s2/Popover';
import {positionToDOMRange, Token, TokenField, TokenProps} from './TokenField';
import {
positionToDOMRange,
Token,
TokenField,
TokenInput,
TokenProps
} from 'react-aria-components/TokenField';
import {PromptFocusContext} from './Chat';
import Send from '@react-spectrum/s2/icons/ArrowUpSend';
import Stop from '@react-spectrum/s2/icons/StopProcessing';
Expand Down Expand Up @@ -336,10 +340,10 @@ export function PromptTokenField(props: PromptTokenFieldProps) {
<TokenField
value={prompt}
onChange={setPrompt}
multiline
allowsNewlines
aria-label="Prompt"
data-placeholder="Ready to get started? Ask a question, share an idea, or add a task."
ref={inputRef}
className=""
onSubmit={onSubmit}
onFocus={e => {
if (e.isTrusted) {
setFocused(true);
Expand Down Expand Up @@ -371,25 +375,28 @@ export function PromptTokenField(props: PromptTokenFieldProps) {
}
}
: undefined
}
onSubmit={onSubmit}
className={renderProps =>
css('&:empty::before { content: attr(data-placeholder); }') +
style({
font: 'body',
color: {
default: baseColor('neutral'),
':empty': {
default: 'gray-600',
forcedColors: 'GrayText'
}
},
width: 'full',
outlineStyle: 'none',
cursor: 'text'
})(renderProps)
}>
{children || (segment => <PromptToken>{segment.text}</PromptToken>)}
<TokenInput
data-placeholder="Ready to get started? Ask a question, share an idea, or add a task."
Comment thread
snowystinger marked this conversation as resolved.
ref={inputRef}
className={renderProps =>
css('&:empty::before { content: attr(data-placeholder); }') +
style({
font: 'body',
color: {
default: baseColor('neutral'),
':empty': {
default: 'gray-600',
forcedColors: 'GrayText'
}
},
width: 'full',
outlineStyle: 'none',
cursor: 'text'
})(renderProps)
}>
{children || (segment => <PromptToken>{segment.text}</PromptToken>)}
</TokenInput>
</TokenField>
<PromptTokenFieldPopover
filterAnchor={filterAnchor}
Expand Down
6 changes: 6 additions & 0 deletions packages/dev/s2-docs/pages/react-aria/Autocomplete.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default Layout;
import docs from 'docs:react-aria-components';
import vanillaDocs from 'docs:vanilla-starter/CommandPalette';
import '../../tailwind/tailwind.css';
import {InlineAlert, Heading, Content} from '@react-spectrum/s2'

export const tags = ['combobox', 'typeahead', 'input'];
export const version = 'rc';
Expand Down Expand Up @@ -110,6 +111,11 @@ export const description = 'Allows users to search or filter a list of suggestio

</ExampleSwitcher>

<InlineAlert variant="informative" UNSAFE_style={{maxWidth: 500}}>
<Heading>Autocomplete vs. ComboBox</Heading>
<Content>Use [ComboBox](ComboBox) to **select one or more values** from a pre-defined set of options. Use Autocomplete to **filter a collection** or provide **text completions**.</Content>
</InlineAlert>

## Content

Autocomplete filters a collection component using a [TextField](TextField) or [SearchField](SearchField). It can be used to build UI patterns such as command palettes, searchable menus, filterable selects, and more.
Expand Down
5 changes: 5 additions & 0 deletions packages/dev/s2-docs/pages/react-aria/ComboBox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ export const description = 'Combines a text input with a listbox, allowing users

</ExampleSwitcher>

<InlineAlert variant="informative" UNSAFE_style={{maxWidth: 500}}>
<Heading>Autocomplete vs. ComboBox</Heading>
<Content>Use ComboBox to **select one or more values** from a pre-defined set of options. Use [Autocomplete](Autocomplete) to **filter a collection** or provide **text completions**.</Content>
</InlineAlert>

## Content

`ComboBox` reuses the `ListBox` component, following the [Collection Components API](collections?component=ComboBox). It supports ListBox features such as static and dynamic collections, sections, disabled items, links, text slots, asynchronous loading, etc. See the [ListBox docs](ListBox) for more details.
Expand Down
59 changes: 59 additions & 0 deletions packages/dev/s2-docs/pages/react-aria/ComboBoxSegmentList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {Key} from '@react-types/shared';
import {TokenSegmentList} from 'react-aria-components/TokenField';

export class ComboBoxSegmentList extends TokenSegmentList<Key> {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do we want to maintain some of these subclasses shown in the docs as official implementations for any of these patterns? Or just rely on copy/paste for now?

getSelectedKeys(): Key[] {
return this.segments.filter(seg => seg.type === 'token').map(seg => seg.value!);
}

getInputValue(): string {
let segment = this.segments[this.caretPosition.index];
return segment?.type === 'text' ? segment.text : '';
}

setSelectedKeys(keys: Key[]): ComboBoxSegmentList {
let selectedKeys = this.getSelectedKeys();
let added: Key[] = [];
for (let key of keys) {
if (!selectedKeys.includes(key)) {
added.push(key);
}
}
let removed = new Set();
for (let key of selectedKeys) {
if (!keys.includes(key)) {
removed.add(key);
}
}

let value = this;
for (let key of removed) {
let index = value.segments.findIndex(seg => seg.type === 'token' && seg.value! === key);
value = value.replaceRangeWithSegments(
{index: index, offset: 0},
{index: index, offset: value.segments[index]?.text.length ?? 0},
[],
false
);
}

if (added.length > 0) {
let segment = value.segments[value.caretPosition.index];
value = value.replaceRangeWithSegments(
{index: value.caretPosition.index, offset: 0},
segment?.type === 'text'
? {
index: value.caretPosition.index,
offset: value.segments[value.caretPosition.index]?.text.length ?? 0
}
: {index: value.caretPosition.index, offset: 0},
added.map(value => {
return {type: 'token', text: String(value), value: value};
}),
false
);
}

return value;
}
}
23 changes: 23 additions & 0 deletions packages/dev/s2-docs/pages/react-aria/TagFieldSegmentList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {type TokenFieldSegment, TokenSegmentList} from 'react-aria-components/TokenField';

export class TagFieldSegmentList extends TokenSegmentList {
tokenize(text: string): TokenFieldSegment[] {
let parts = text.split(/[, \n]/);

let segments: TokenFieldSegment[] = parts.map((part, i) => {
if (i === parts.length - 1 || part.length === 0) {
return {type: 'text', text: part};
}
return {type: 'token', text: part};
});

if (parts.at(-1)?.length === 0) {
segments.pop();
}
return segments;
}

toString(): string {
return this.segments.map(seg => seg.text).join(', ');
}
}
Loading