From 0544a4f6df48c972c563eb966e6fb93839b6b15d Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Tue, 5 May 2026 15:29:46 -0700 Subject: [PATCH 01/13] base setup, transferring the stuff from the prototypes repo --- packages/@react-spectrum/s2/src/Thread.tsx | 69 +++ .../s2/stories/Thread.stories.tsx | 437 ++++++++++++++++++ 2 files changed, 506 insertions(+) create mode 100644 packages/@react-spectrum/s2/src/Thread.tsx create mode 100644 packages/@react-spectrum/s2/stories/Thread.stories.tsx diff --git a/packages/@react-spectrum/s2/src/Thread.tsx b/packages/@react-spectrum/s2/src/Thread.tsx new file mode 100644 index 00000000000..aaeab66ebfc --- /dev/null +++ b/packages/@react-spectrum/s2/src/Thread.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {DOMRef, forwardRefType} from '@react-types/shared'; +import {forwardRef, ReactNode, useEffect} from 'react'; +import {GridList} from 'react-aria-components/GridList'; +import {style} from '../style' with {type: 'macro'}; +import {useDOMRef} from './useDOMRef'; + +interface ThreadProps { + // TODO: should take specific children (UserMessage/etc), but those are to come + children: ReactNode | ((item) => ReactNode) +}; + +// TODO: things to look at +// chatgpt, claude, other AI assistants to see their UX + + +// TODO: things to figure out/try +// scroll to bottom button +// announcements for new messages +// column reverse layout? +// add to story some kind of mock streaming + + +// TODO: things to handle later +export const Thread = /*#__PURE__*/ (forwardRef as forwardRefType)(function Thread( + props: ThreadProps, + ref: DOMRef +) { + let {children} = props; + let domRef = useDOMRef(ref); + + useEffect(() => { + requestAnimationFrame(() => { + if (domRef.current) { + domRef.current.scrollTop = domRef.current.scrollHeight; + } + }); + }, [domRef]); + + return ( + + {children} + + ); +}); diff --git a/packages/@react-spectrum/s2/stories/Thread.stories.tsx b/packages/@react-spectrum/s2/stories/Thread.stories.tsx new file mode 100644 index 00000000000..8f09af0036e --- /dev/null +++ b/packages/@react-spectrum/s2/stories/Thread.stories.tsx @@ -0,0 +1,437 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {ActionButton} from '../src/ActionButton'; +import {ActionMenu} from '../src/ActionMenu'; +import {AssetCard} from '../src/Card'; +import {baseColor, css, focusRing, style} from '../style' with {type: 'macro'}; +import {Button} from '../src/Button'; +import {ButtonContext, GridListItem, Group, isFileDropItem, Label, Tag, TagGroup, TagList, TextArea, TextField, useDrop} from 'react-aria-components'; +import {Card, CardPreview} from '../src/Card'; +import CheckmarkCircle from '@react-spectrum/s2/icons/CheckmarkCircle'; +import ChevronRight from '@react-spectrum/s2/icons/ArrowCurved'; +import {CloseButton} from '../src/CloseButton'; +import {Content, Text} from '../src/Content'; +import {Disclosure, DisclosureHeader, DisclosurePanel, DisclosureTitle} from '../src/Disclosure'; +import {Image} from '../src/Image'; +import {Link, LinkProps} from '../src/Link'; +import {MenuItem} from '../src/Menu'; +import type {Meta} from '@storybook/react'; +import Plus from '@react-spectrum/s2/icons/Add'; +import {ProgressCircle} from '../src/ProgressCircle'; +import {ReactNode, useRef, useState} from 'react'; +import Send from '@react-spectrum/s2/icons/ArrowUpSend'; +import {Thread} from '../src/Thread'; +import ThumbDown from '@react-spectrum/s2/icons/ThumbDown'; +import ThumbUp from '@react-spectrum/s2/icons/ThumbUp'; +import {ToggleButton} from '../src/ToggleButton'; +import {ToggleButtonGroup} from '../src/ToggleButtonGroup'; + +const meta: Meta = { + component: Thread, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] + // argTypes: { + // ...categorizeArgTypes('Events', ['onSelectionChange']), + // children: {table: {disable: true}} + // }, + // title: 'Thread', + // args: { + // styles: style({height: 320}) + // }, + // decorators: [ + // (Story) => ( + //
+ // + //
+ // ) + // ] +}; + +export default meta; +// type Story = StoryObj; + +export function StaticThread() { + return ( +
+ + + + + + + + Hilton commercial assets + + Edit + Share + Delete + + 2026 + + + + Can you help me create a 45-minute presentation, with animations, for an executive update? + + + + + + + Desert Sunset + + Edit + Share + Delete + + PNG • 2/3/2024 + + + + Can you help me create a 45-minute presentation, with animations, for an executive update? + + +
+

Big idea/core narrative: The warmth of welcome

+

Hospitality begins the moment our customers set foot off their plane. We are more than accommodation, and we service a diverse base. We hope to be the anchor and bounce board for all who stay with us.

+

Belonging happens at Hilton

+

We strive to be familiar but exceed expectations. These assets highlight how belonging is personified.

+

We are more than accommodation

+
    +
  • Airport pick up service
  • +
  • Local recommendations
  • +
  • Everyday excursions
  • +
  • Customizable experience
  • +
+
+ + + + Hilton brand email — Q1 campaign 2026 + Market research — hospitality trends 2025 + User research — loyalty programme survey + + +
+ Can you help me create a 45-minute presentation, with animations, for an executive update? + + +

What would you like to do next?

+
+ + + +
+
+
+ +
+ ); +} + +// TODO: make a streaming example that adds something on end (make it a random reply) +// function DynamicThread() { + +// } + +// TODO: all of the below was copied from rsp-prototypes, just filler for now +function PromptField() { + let [attachments, setAttachments] = useState([ + { + image: 'https://react-spectrum.adobe.com/preview.c3b340d3.png', + title: 'Hilton assets', + description: '2026' + } + ]); + + // Not using RAC DropZone because it adds its own focusable button, + // and we want to avoid an extra tab stop by attaching to the input. + // TODO: support clipboard too (without messing up pasting text) + let inputRef = useRef(null); + let {dropProps, dropButtonProps, isDropTarget} = useDrop({ + ref: inputRef, + hasDropButton: true, + async onDrop(e) { + let files = await Promise.all(e.items.filter(isFileDropItem).map(async item => ({ + image: item.type.startsWith('image/') ? URL.createObjectURL(await item.getFile()) : '', + title: item.name, + description: item.type + }))); + setAttachments(attachments => [...attachments, ...files]); + } + }); + + return ( +
+ style({ + ...focusRing(), + padding: 16, + boxShadow: 'emphasized', + backgroundColor: { + default: 'elevated', + isDropTarget: 'blue-200' + }, + borderRadius: 'lg', + borderWidth: 2, + borderStyle: 'solid', + borderColor: { + default: 'transparent', + isFocusWithin: 'gray-900', + isDropTarget: 'blue-800' + } + })({...renderProps, isDropTarget})}> + + {attachments.map((attachment, i) => ( + { + setAttachments(attachments.slice(0, i).concat(attachments.slice(i + 1))); + }} /> + ))} + + + +