Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .storybook-s2/docs/Migrating.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ export function Migrating() {
<li className={style({font: 'body', marginY: 8})}>If within <Code>Picker</Code>: Update <Code>Item</Code> to be a <Code>PickerItem</Code></li>
<li className={style({font: 'body', marginY: 8})}>If within <Code>ComboBox</Code>: Update <Code>Item</Code> to be a <Code>ComboBoxItem</Code></li>
<li className={style({font: 'body', marginY: 8})}>If within <Code>ListBox</Code>: Update <Code>Item</Code> to be a <Code>ListBoxItem</Code></li>
<li className={style({font: 'body', marginY: 8})}>If within <Code>ListView</Code>: Update <Code>Item</Code> to be a <Code>ListViewItem</Code></li>
<li className={style({font: 'body', marginY: 8})}>If within <Code>TabList</Code>: Update <Code>Item</Code> to be a <Code>Tab</Code></li>
<li className={style({font: 'body', marginY: 8})}>If within <Code>TabPanels</Code>: Update <Code>Item</Code> to be a <Code>TabPanel</Code> and remove surrounding <Code>TabPanels</Code></li>
<li className={style({font: 'body', marginY: 8})}>Update <Code>key</Code> to be <Code>id</Code> (and keep <Code>key</Code> if rendered inside <Code>array.map</Code>)</li>
Expand All @@ -275,6 +276,12 @@ export function Migrating() {
<li className={style({font: 'body', marginY: 8})}>Update <Code>Item</Code> to be a <Code>ListBoxItem</Code></li>
</ul>

<H3>ListView</H3>
<ul className="sb-unstyled">
<li className={style({font: 'body', marginY: 8})}>[PENDING] Comment out <Code>density</Code> (it has not been implemented yet)</li>
<li className={style({font: 'body', marginY: 8})}>[PENDING] Comment out <Code>dragAndDropHooks</Code> (it has not been implemented yet)</li>
</ul>

<H3>Menu</H3>
<ul className="sb-unstyled">
<li className={style({font: 'body', marginY: 8})}>Update <Code>Item</Code> to be a <Code>MenuItem</Code></li>
Expand Down
255 changes: 255 additions & 0 deletions packages/@react-spectrum/s2/chromatic/ListView.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*
* 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, ActionButtonGroup, ActionMenu, Content, Heading, IllustratedMessage, Image, ListView, ListViewItem, MenuItem, Text} from '../src';
import {checkers} from './check';
import Delete from '../s2wf-icons/S2_Icon_Delete_20_N.svg';
import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg';
import File from '../s2wf-icons/S2_Icon_File_20_N.svg';
import Folder from '../s2wf-icons/S2_Icon_Folder_20_N.svg';
import FolderOpen from '../spectrum-illustrations/linear/FolderOpen';
import type {Meta, StoryObj} from '@storybook/react';
import {style} from '../style/spectrum-theme' with {type: 'macro'};

const meta: Meta<typeof ListView> = {
component: ListView,
parameters: {
chromaticProvider: {disableAnimations: true}
},
title: 'S2 Chromatic/ListView'
};

export default meta;
type Story = StoryObj<typeof ListView>;

let listViewStyles = style({width: 320, height: 320});

const items = [
{id: 'utilities', name: 'Utilities', type: 'folder', description: '16 items'},
{id: 'photoshop', name: 'Adobe Photoshop', type: 'file', description: 'Application'},
{id: 'illustrator', name: 'Adobe Illustrator', type: 'file', description: 'Application'},
{id: 'xd', name: 'Adobe XD', type: 'file', description: 'Application'}
];

export const Example: Story = {
render: (args) => (
<ListView {...args} aria-label="Files" styles={listViewStyles}>
<ListViewItem id="utilities" textValue="Utilities" hasChildItems>
<Folder />
<Text>Utilities</Text>
<Text slot="description">16 items</Text>
</ListViewItem>
<ListViewItem id="photoshop">Adobe Photoshop</ListViewItem>
<ListViewItem id="illustrator">Adobe Illustrator</ListViewItem>
<ListViewItem id="xd">Adobe XD</ListViewItem>
</ListView>
),
args: {
selectionMode: 'multiple',
onLoadMore: undefined
}
};

export const HighlightSelection: Story = {
...Example,
args: {
...Example.args,
selectionStyle: 'highlight',
selectedKeys: ['photoshop', 'illustrator']
}
};

export const Quiet: Story = {
...Example,
args: {
...Example.args,
isQuiet: true
}
};

export const WithImages: Story = {
render: (args) => (
<ListView {...args} aria-label="Images" styles={listViewStyles} items={items}>
{(item) => (
<ListViewItem textValue={item.name}>
<Text>{item.name}</Text>
<Image src={checkers} alt={item.name} />
</ListViewItem>
)}
</ListView>
)
};

export const OverflowTruncate: Story = {
render: (args) => (
<ListView {...args} aria-label="Long text examples" styles={listViewStyles}>
<ListViewItem id="a">
This is a very very very very very very very very long title.
</ListViewItem>
<ListViewItem id="b" textValue="Short title, long description">
<Text>Short title</Text>
<Text slot="description">This is a very very very very very very very very long description.</Text>
</ListViewItem>
<ListViewItem id="c" textValue="Long title, long description">
<Text>This is a very very very very very very very very long title.</Text>
<Text slot="description">This is a very very very very very very very very long description.</Text>
</ListViewItem>
</ListView>
),
args: {
...Example.args,
overflowMode: 'truncate'
}
};

export const OverflowWrap: Story = {
render: (args) => (
<ListView {...args} aria-label="Long text examples" styles={listViewStyles}>
<ListViewItem id="a">
This is a very very very very very very very very long title.
</ListViewItem>
<ListViewItem id="b" textValue="Short title, long description">
<Text>Short title</Text>
<Text slot="description">This is a very very very very very very very very long description.</Text>
</ListViewItem>
<ListViewItem id="c" textValue="Long title, long description">
<Text>This is a very very very very very very very very long title.</Text>
<Text slot="description">This is a very very very very very very very very long description.</Text>
</ListViewItem>
</ListView>
),
args: {
...Example.args,
overflowMode: 'wrap'
}
};

export const DisabledItems: Story = {
render: (args) => (
<ListView {...args} aria-label="Files" items={items} styles={listViewStyles} selectionMode="multiple" disabledKeys={['utilities', 'illustrator']}>
{(item) => (
<ListViewItem textValue={item.name} hasChildItems={item.type === 'folder'}>
{item.type === 'folder' ? <Folder /> : <File />}
<Text>{item.name}</Text>
<Text slot="description">{item.description}</Text>
</ListViewItem>
)}
</ListView>
)
};

export const DisabledBehaviorSelection: Story = {
...DisabledItems,
args: {
disabledBehavior: 'selection'
}
};

export const CheckboxSelection: Story = {
...Example,
args: {
...Example.args,
selectionStyle: 'checkbox',
selectedKeys: ['photoshop', 'illustrator']
}
};

export const Links: Story = {
render: (args) => (
<ListView {...args} aria-label="Bookmarks" styles={listViewStyles}>
<ListViewItem id="https://adobe.com/" href="https://adobe.com/" target="_blank">Adobe</ListViewItem>
<ListViewItem id="https://google.com/" href="https://google.com/" target="_blank">Google</ListViewItem>
<ListViewItem id="https://apple.com/" href="https://apple.com/" target="_blank">Apple</ListViewItem>
<ListViewItem id="https://nytimes.com/" href="https://nytimes.com/" target="_blank">New York Times</ListViewItem>
</ListView>
),
args: {
selectionMode: 'none'
}
};

export const WithActions: Story = {
render: (args) => (
<ListView {...args} aria-label="Files" styles={listViewStyles}>
<ListViewItem id="utilities" textValue="Utilities" hasChildItems>
<Folder />
<Text>Utilities</Text>
<Text slot="description">16 items</Text>
<ActionButtonGroup>
<ActionButton aria-label="Edit"><Edit /></ActionButton>
</ActionButtonGroup>
<ActionMenu>
<MenuItem id="edit"><Edit /><Text>Edit</Text></MenuItem>
<MenuItem id="delete"><Delete /><Text>Delete</Text></MenuItem>
</ActionMenu>
</ListViewItem>
<ListViewItem id="photoshop" textValue="Adobe Photoshop">
<Text>Adobe Photoshop</Text>
<Text slot="description">Application</Text>
<ActionButtonGroup>
<ActionButton aria-label="Edit"><Edit /></ActionButton>
</ActionButtonGroup>
<ActionMenu>
<MenuItem id="edit"><Edit /><Text>Edit</Text></MenuItem>
<MenuItem id="delete"><Delete /><Text>Delete</Text></MenuItem>
</ActionMenu>
</ListViewItem>
</ListView>
),
args: {
selectionMode: 'single'
}
};

export const Loading: Story = {
render: (args) => (
<ListView {...args} aria-label="Loading list" styles={listViewStyles}>
{[]}
</ListView>
),
args: {
loadingState: 'loading'
}
};

export const LoadingMore: Story = {
render: (args) => (
<ListView {...args} aria-label="Loading more list" styles={listViewStyles} items={items} onLoadMore={() => {}}>
{(item) => (
<ListViewItem textValue={item.name}>
{item.name}
</ListViewItem>
)}
</ListView>
),
args: {
loadingState: 'loadingMore'
}
};

export const EmptyState: Story = {
render: (args) => (
<ListView
{...args}
aria-label="Empty list"
styles={listViewStyles}
renderEmptyState={() => (
<IllustratedMessage>
<FolderOpen />
<Heading>No results</Heading>
<Content>No results found.</Content>
</IllustratedMessage>
)}>
{[]}
</ListView>
)
};
2 changes: 2 additions & 0 deletions packages/@react-spectrum/s2/chromatic/check.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export let checkers = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTAuMTAwMDk4IDBIMy4xMDAxVjNIMC4xMDAwOThWMFoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTAuMTAwMDk4IDE4SDMuMTAwMVYyMUgwLjEwMDA5OFYxOFoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTYuMTAwMSAwSDkuMTAwMVYzSDYuMTAwMVYwWiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNNi4xMDAxIDE4SDkuMTAwMVYyMUg2LjEwMDFWMThaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0xMi4xMDAxIDE4SDkuMTAwMVYxNUgxMi4xMDAxVjE4WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMTIuMTAwMSAyNEg5LjEwMDFWMjFIMTIuMTAwMVYyNFoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTE1LjEwMDEgMEgxMi4xMDAxVjNIMTUuMTAwMVYwWiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMTUuMTAwMSAxOEgxMi4xMDAxVjIxSDE1LjEwMDFWMThaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0xOC4xMDAxIDE4SDE1LjEwMDFWMTVIMTguMTAwMVYxOFoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTE4LjEwMDEgMjRIMTUuMTAwMVYyMUgxOC4xMDAxVjI0WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMjEuMTAwMSAwSDE4LjEwMDFWM0gyMS4xMDAxVjBaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0yMS4xMDAxIDE4SDE4LjEwMDFWMjFIMjEuMTAwMVYxOFoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTI0LjEwMDEgMThIMjEuMTAwMVYxNUgyNC4xMDAxVjE4WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMjEuMTAwMSAyMUgyNC4xMDAxVjI0SDIxLjEwMDFWMjFaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik02LjEwMDEgMThIMy4xMDAxVjE1SDYuMTAwMVYxOFoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTYuMTAwMSAyNEgzLjEwMDFWMjFINi4xMDAxVjI0WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMC4xMDAwOTggNkgzLjEwMDFWM0gwLjEwMDA5OFY2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTYuMTAwMSA2SDkuMTAwMVYzSDYuMTAwMVY2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjEwMDEgMTJIOS4xMDAxVjE1SDEyLjEwMDFWMTJaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTUuMTAwMSA2SDEyLjEwMDFWM0gxNS4xMDAxVjZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTguMTAwMSAxMkgxNS4xMDAxVjE1SDE4LjEwMDFWMTJaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjEuMTAwMSA2SDE4LjEwMDFWM0gyMS4xMDAxVjZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjQuMTAwMSAxMkgyMS4xMDAxVjE1SDI0LjEwMDFWMTJaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNi4xMDAxIDEySDMuMTAwMVYxNUg2LjEwMDFWMTJaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMC4xMDAwOTggOUgzLjEwMDFWNkgwLjEwMDA5OFY5WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNNi4xMDAxIDlIOS4xMDAxVjZINi4xMDAxVjlaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0xMi4xMDAxIDlIOS4xMDAxVjEySDEyLjEwMDFWOVoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTE1LjEwMDEgOUgxMi4xMDAxVjZIMTUuMTAwMVY5WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMTguMTAwMSA5SDE1LjEwMDFWMTJIMTguMTAwMVY5WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMjEuMTAwMSA5SDE4LjEwMDFWNkgyMS4xMDAxVjlaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0yNC4xMDAxIDlIMjEuMTAwMVYxMkgyNC4xMDAxVjlaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik02LjEwMDEgOUgzLjEwMDFWMTJINi4xMDAxVjlaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0wLjEwMDA5OCAxMkgzLjEwMDFWOUgwLjEwMDA5OFYxMloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik02LjEwMDEgMTJIOS4xMDAxVjlINi4xMDAxVjEyWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjEwMDEgNkg5LjEwMDFWOUgxMi4xMDAxVjZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTUuMTAwMSAxMkgxMi4xMDAxVjlIMTUuMTAwMVYxMloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xOC4xMDAxIDZIMTUuMTAwMVY5SDE4LjEwMDFWNloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS4xMDAxIDEySDE4LjEwMDFWOUgyMS4xMDAxVjEyWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTI0LjEwMDEgNkgyMS4xMDAxVjlIMjQuMTAwMVY2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTYuMTAwMSA2SDMuMTAwMVY5SDYuMTAwMVY2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTAuMTAwMDk4IDE1SDMuMTAwMVYxMkgwLjEwMDA5OFYxNVoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTYuMTAwMSAxNUg5LjEwMDFWMTJINi4xMDAxVjE1WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMTIuMTAwMSAzSDkuMTAwMVY2SDEyLjEwMDFWM1oiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTE1LjEwMDEgMTVIMTIuMTAwMVYxMkgxNS4xMDAxVjE1WiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNMTguMTAwMSAzSDE1LjEwMDFWNkgxOC4xMDAxVjNaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0yMS4xMDAxIDE1SDE4LjEwMDFWMTJIMjEuMTAwMVYxNVoiIGZpbGw9IiNFMUUxRTEiLz4KPHBhdGggZD0iTTI0LjEwMDEgM0gyMS4xMDAxVjZIMjQuMTAwMVYzWiIgZmlsbD0iI0UxRTFFMSIvPgo8cGF0aCBkPSJNNi4xMDAxIDNIMy4xMDAxVjZINi4xMDAxVjNaIiBmaWxsPSIjRTFFMUUxIi8+CjxwYXRoIGQ9Ik0wLjEwMDA5OCAxOEgzLjEwMDFWMTVIMC4xMDAwOThWMThaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMC4xMDAwOTggMjRIMy4xMDAxVjIxSDAuMTAwMDk4VjI0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTYuMTAwMSAxOEg5LjEwMDFWMTVINi4xMDAxVjE4WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTYuMTAwMSAyNEg5LjEwMDFWMjFINi4xMDAxVjI0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjEwMDEgMEg5LjEwMDFWM0gxMi4xMDAxVjBaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTIuMTAwMSAxOEg5LjEwMDFWMjFIMTIuMTAwMVYxOFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNS4xMDAxIDE4SDEyLjEwMDFWMTVIMTUuMTAwMVYxOFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNS4xMDAxIDI0SDEyLjEwMDFWMjFIMTUuMTAwMVYyNFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xOC4xMDAxIDBIMTUuMTAwMVYzSDE4LjEwMDFWMFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xOC4xMDAxIDE4SDE1LjEwMDFWMjFIMTguMTAwMVYxOFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS4xMDAxIDE4SDE4LjEwMDFWMTVIMjEuMTAwMVYxOFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS4xMDAxIDI0SDE4LjEwMDFWMjFIMjEuMTAwMVYyNFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMS4xMDAxIDNIMjQuMTAwMVYwSDIxLjEwMDFWM1oiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yNC4xMDAxIDE4SDIxLjEwMDFWMjFIMjQuMTAwMVYxOFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik02LjEwMDEgMEgzLjEwMDFWM0g2LjEwMDFWMFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik02LjEwMDEgMThIMy4xMDAxVjIxSDYuMTAwMVYxOFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=';
Loading
Loading