Skip to content

feat: add AttachmentGrid component - #10561

Open
DPandyan wants to merge 9 commits into
mainfrom
attachmentgrid
Open

DPandyan wants to merge 9 commits into
mainfrom
attachmentgrid

Conversation

@DPandyan

@DPandyan DPandyan commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Closes

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices
  • I understand every change in this PR and can explain why it's there.
  • If AI-assisted, I followed our AI contribution guidance and pointed my assistant at CLAUDE.md.

📝 Test Instructions:

🧢 Your Project:

Adobe

@DPandyan DPandyan changed the title add: AttachmentGrid component feat: add AttachmentGrid component Sep 3, 2026
@rspbot

rspbot commented Sep 3, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Sep 3, 2026

Copy link
Copy Markdown

...focusRing()
});

const gridGap = css('gap: 6px;');

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I can swap this out for 8px, Figma had 6, wasn't sure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you can use custom values in a style macro like so
gap: '[6px]'
or if it needs to respond to font size or scaling, you can make use of size() or space()
https://react-spectrum.adobe.com/style-macro#space

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is it really a static 6px at all sizes? Feels like the scale should change the gap size

)
};

export const WithAttachmentGrid: Story = {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thought this would be a useful story, can remove.

export const Overflow: Story = {
name: 'Overflow (vertical scroll fade)',
render: args => (
<div style={{width: 320, resize: 'horizontal', overflow: 'hidden'}}>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

maybe this needs padding

@LFDanLu LFDanLu added the release label Sep 3, 2026
@rspbot

rspbot commented Sep 3, 2026

Copy link
Copy Markdown

@DPandyan
DPandyan marked this pull request as ready for review September 3, 2026 17:55
...focusRing()
});

const gridGap = css('gap: 6px;');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is it really a static 6px at all sizes? Feels like the scale should change the gap size

let domRef = useDOMRef(ref);

return (
<ListBox

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should the container have a focus ring? Or are the items focusable its hard to tell

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think the individual items should be focusable/interactable but perhaps the grid/container itself should have a focus ring. You are supposed to be able to scroll it with keyboard when focused.

const hasContent = ':has([data-slot=content])';

const gridStyles = style({
display: 'grid',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can there be a mix of cards that are thumbnails and cards that have the description?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

no, the card types can't be mixed (at least following AttachmentList)

const gridStyles = style({
display: 'grid',
gridTemplateColumns: {
default: 'repeat(auto-fill, minmax(64px, 1fr))',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If the container is wide and there aren't enough thumbs to fill it in the first row, i think they'll end up spaced apart
I think you just want

default: 'repeat(auto-fill, minmax(64px, auto))'

though that only matters if the attachments can be varying sizes, if they can't, then you could just do

default: 'repeat(auto-fill, 64px)'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We had a discussion about this in one of our team meetings. We're unsure if it should be justified space around as you have it here, or left justified... or what, can you follow up with the designer?

size?: 'XS' | 'S' | 'M' | 'L' | 'XL';
/** Whether the attachment has an error. */
isInvalid?: boolean;
uploadProgress?: number;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

missing description

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

bump on this, still missing a description. Mind adding some docs for this as well? Can just go under the ai-components docs

Comment on lines +140 to +142
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
aria-describedby={ariaDescribedby}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

these and id are all just getting passed straight through, we can use filterDOMProps with labeling set to true
then we can just spread the result of that

@LFDanLu LFDanLu removed the release label Sep 3, 2026
@rspbot

rspbot commented Sep 9, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Sep 11, 2026

Copy link
Copy Markdown

@LFDanLu LFDanLu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks pretty good, just a couple of small comments

overflowY: 'auto',
overflowX: 'clip',
boxSizing: 'border-box',
...focusRing()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

doesn't seem like this focus ring is appearing on the grid, maybe getting clipped or something?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I figured out what was happening, it was being clipped and even when I added some offset it needed some padding so that it didn't overlap with the grid items.

}
},
gap: 8,
maxHeight: 240,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this height defined explicitly in the design or should it be customizable by the user?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So in the designs a bunch of the examples had a maxHeight of 240, but one of them was 190. I swapped it to inherit, that probably makes more sense.

size?: 'XS' | 'S' | 'M' | 'L' | 'XL';
/** Whether the attachment has an error. */
isInvalid?: boolean;
uploadProgress?: number;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

bump on this, still missing a description. Mind adding some docs for this as well? Can just go under the ai-components docs

@github-actions github-actions Bot added the S2 label Sep 24, 2026
@rspbot

rspbot commented Sep 24, 2026

Copy link
Copy Markdown

overflowY: 'auto',
overflowX: 'clip',
boxSizing: 'border-box',
...focusRing(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: should the focus ring have rounded borders? dunno, feel like everything in S2 is rounded so 🤷‍♀️

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ik there aren't designs for this so probably fine to leave it as is

/**
* An AttachmentGrid displays file attachments as a wrapping, vertically-scrolling grid of
* thumbnails. Unlike AttachmentList, it is display-only and does not support selection or removal.
* Every attachment is disabled, so the grid itself becomes the sole tab stop, keeping the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need this part (aka "every attachment is disabled...")? i feel like it's sorta implied?


#### AttachmentGrid

Use `AttachmentGrid` to display file attachments as a wrapping, vertically-scrolling grid of

@yihuiliao yihuiliao Sep 24, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wonder if we should have an example of this in a user message somewhere

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I originally had it as part of the UserMessage's story but wasn't sure if that was a good place.

@rspbot

rspbot commented Sep 24, 2026

Copy link
Copy Markdown

@rspbot

rspbot commented Sep 24, 2026

Copy link
Copy Markdown
## API Changes

@react-spectrum/ai

/@react-spectrum/ai:AttachmentGrid

+AttachmentGrid <T> {
+  align?: 'start' | 'center' | 'end' = 'start'
+  aria-describedby?: string
+  aria-details?: string
+  aria-label?: string
+  aria-labelledby?: string
+  children?: ReactNode | (T) => ReactNode
+  dependencies?: ReadonlyArray<any>
+  id?: string
+  items?: Iterable<T>
+  styles?: StyleString
+}

/@react-spectrum/ai:AttachmentGridItem

+AttachmentGridItem {
+  aria-describedby?: string
+  aria-details?: string
+  aria-label?: string
+  aria-labelledby?: string
+  children: ReactNode
+  id?: Key
+  isInvalid?: boolean
+  size?: 'XS' | 'S' | 'M' | 'L' | 'XL'
+  styles?: StyleString
+  textValue?: string
+  uploadProgress?: number
+}

/@react-spectrum/ai:AttachmentGridProps

+AttachmentGridProps <T> {
+  align?: 'start' | 'center' | 'end' = 'start'
+  aria-describedby?: string
+  aria-details?: string
+  aria-label?: string
+  aria-labelledby?: string
+  children?: ReactNode | (T) => ReactNode
+  dependencies?: ReadonlyArray<any>
+  id?: string
+  items?: Iterable<T>
+  styles?: StyleString
+}

/@react-spectrum/ai:AttachmentGridItemProps

+AttachmentGridItemProps {
+  aria-describedby?: string
+  aria-details?: string
+  aria-label?: string
+  aria-labelledby?: string
+  children: ReactNode
+  id?: Key
+  isInvalid?: boolean
+  size?: 'XS' | 'S' | 'M' | 'L' | 'XL'
+  styles?: StyleString
+  textValue?: string
+  uploadProgress?: number
+}

@rspbot

rspbot commented Sep 24, 2026

Copy link
Copy Markdown

Agent Skills Changes

Modified (13)
Install

React Spectrum S2:

npx skills add https://d1pzu54gtk2aed.cloudfront.net/pr/8d1c968a9661f4bc012de91d00e4e03062bf5efb/

React Aria:

npx skills add https://d5iwopk28bdhl.cloudfront.net/pr/8d1c968a9661f4bc012de91d00e4e03062bf5efb/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: 👀 In Review

Development

Successfully merging this pull request may close these issues.

5 participants