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
2 changes: 1 addition & 1 deletion examples/s2-esbuild-starter-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Edit the settings.mjs to add an import for the plugin and add the plugin to `plu
import macrosPlugin from 'unplugin-parcel-macros';
...
plugins: [
macros.esbuild(),
macrosPlugin.esbuild(),
esbuildPluginTsc({
force: true
}),
Expand Down
110 changes: 110 additions & 0 deletions examples/s2-parcel-example/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import React, { useState } from "react";
import "@react-spectrum/s2/page.css";
import {
Accordion,
ActionButton,
ActionMenu,
AlertDialog,
Expand All @@ -23,6 +24,7 @@ import {
Breadcrumbs,
Button,
ButtonGroup,
Cell,
Checkbox,
CheckboxGroup,
ColorArea,
Expand All @@ -31,13 +33,18 @@ import {
ColorSwatch,
ColorSwatchPicker,
ColorWheel,
Column,
ComboBox,
ComboBoxItem,
Content,
ContextualHelp,
Dialog,
DialogContainer,
DialogTrigger,
Disclosure,
DisclosureHeader,
DisclosurePanel,
DisclosureTitle,
Divider,
DropZone,
Footer,
Expand All @@ -62,12 +69,18 @@ import {
Radio,
RadioGroup,
RangeSlider,
Row,
SearchField,
SegmentedControl,
SegmentedControlItem,
Slider,
StatusLight,
SubmenuTrigger,
Switch,
Tab,
TableView,
TableBody,
TableHeader,
TabList,
TabPanel,
Tabs,
Expand All @@ -85,9 +98,26 @@ import Cloud from "@react-spectrum/s2/illustrations/linear/Cloud";
import DropToUpload from "@react-spectrum/s2/illustrations/linear/DropToUpload";
import Section from "./components/Section";
import { style } from "@react-spectrum/s2/style" with { type: "macro" };
import { CardViewExample } from "./components/CardViewExample";
import { CollectionCardsExample } from "./components/CollectionCardsExample";

function App() {
let [isDialogOpen, setIsDialogOpen] = useState(false);
let [cardViewState, setCardViewState] = useState({
layout: 'grid',
loadingState: 'idle',
});
let cardViewLoadingOptions = [
{id: 'idle', label: 'Idle'},
{id: 'loading', label: 'Loading'},
{id: 'sorting', label: 'Sorting'},
{id: 'loadingMore', label: 'Loading More'},
{id: 'error', label: 'Error'},
];
let cardViewLayoutOptions = [
{id: 'grid', label: 'Grid'},
{id: 'waterfall', label: 'Waterfall'}
];
return (
<main>
<Heading
Expand Down Expand Up @@ -136,6 +166,23 @@ function App() {
<MenuItem>Action Menu Item 2</MenuItem>
<MenuItem>Action Menu Item 3</MenuItem>
</ActionMenu>
<Picker
label="CardView Loading State"
items={cardViewLoadingOptions}
selectedKey={cardViewState.loadingState}
onSelectionChange={loadingState => setCardViewState({...cardViewState, loadingState})}>
{item => <PickerItem id={item.id}>{item.label}</PickerItem>}
</Picker>
<Picker
label="CardView Layout"
items={cardViewLayoutOptions}
selectedKey={cardViewState.layout}
onSelectionChange={layout => setCardViewState({...cardViewState, layout})}>
{item => <PickerItem id={item.id}>{item.label}</PickerItem>}
</Picker>
<CardViewExample {...cardViewState} />
<Divider styles={style({maxWidth: 320, width: '100%', marginX: 'auto'})} />
<CollectionCardsExample loadingState={cardViewState.loadingState} />
<MenuTrigger>
<ActionButton>Menu</ActionButton>
<Menu onAction={(key) => alert(key.toString())}>
Expand Down Expand Up @@ -171,6 +218,38 @@ function App() {
<MenuItem>Paste</MenuItem>
</Menu>
</MenuTrigger>
<TableView aria-label="Files" styles={style({width: 320, height: 320})}>
<TableHeader>
<Column isRowHeader>Name</Column>
<Column>Type</Column>
<Column>Date Modified</Column>
<Column>A</Column>
<Column>B</Column>
</TableHeader>
<TableBody>
<Row id="1">
<Cell>Games</Cell>
<Cell>File folder</Cell>
<Cell>6/7/2020</Cell>
<Cell>Dummy content</Cell>
<Cell>Long long long long long long long cell</Cell>
</Row>
<Row id="2">
<Cell>Program Files</Cell>
<Cell>File folder</Cell>
<Cell>4/7/2021</Cell>
<Cell>Dummy content</Cell>
<Cell>Long long long long long long long cell</Cell>
</Row>
<Row id="3">
<Cell>bootmgr</Cell>
<Cell>System file</Cell>
<Cell>11/20/2010</Cell>
<Cell>Dummy content</Cell>
<Cell>Long long long long long long long cell</Cell>
</Row>
</TableBody>
</TableView>
</Section>

<Section title="Color">
Expand Down Expand Up @@ -223,6 +302,31 @@ function App() {
</Section>

<Section title="Navigation">
<div className={style({ minHeight: 176 })}>
<Accordion>
<Disclosure id="files">
<DisclosureHeader>
<DisclosureTitle>
Files
</DisclosureTitle>
<ActionButton><Edit aria-label="Edit" /></ActionButton>
</DisclosureHeader>
<DisclosurePanel>
Files content
</DisclosurePanel>
</Disclosure>
<Disclosure id="people">
<DisclosureHeader>
<DisclosureTitle>
People
</DisclosureTitle>
</DisclosureHeader>
<DisclosurePanel>
<TextField label="Name" styles={style({ maxWidth: 176 })} />
</DisclosurePanel>
</Disclosure>
</Accordion>
</div>
<Breadcrumbs>
<Breadcrumb id="home">Home</Breadcrumb>
<Breadcrumb id="trendy">Trendy</Breadcrumb>
Expand All @@ -236,6 +340,12 @@ function App() {
The missing link.
</Link>
<Link href="/foo">Foo</Link>
<SegmentedControl aria-label="Time granularity" styles={style({width: 384})}>
<SegmentedControlItem id="day">Day</SegmentedControlItem>
<SegmentedControlItem id="week">Week</SegmentedControlItem>
<SegmentedControlItem id="month">Month</SegmentedControlItem>
<SegmentedControlItem id="year">Year</SegmentedControlItem>
</SegmentedControl>
<Tabs aria-label="History of Ancient Rome">
<TabList>
<Tab id="FoR">Founding of Rome</Tab>
Expand Down
187 changes: 187 additions & 0 deletions examples/s2-parcel-example/src/components/CardViewExample.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* 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 { ActionMenu, Avatar, Card, CardPreview, CardView, Collection, CollectionCardPreview, Content, Image, MenuItem, SkeletonCollection, Text } from '@react-spectrum/s2';
import Folder from '@react-spectrum/s2/icons/Folder';
import ErrorIcon from '@react-spectrum/s2/illustrations/linear/AlertNotice';
import { style } from "@react-spectrum/s2/style" with { type: "macro" };
import { useAsyncList } from 'react-stately';

const cardViewStyles = style({
width: 'full',
maxWidth: '[800px]',
height: '[800px]',
margin: 32
});

const avatarSize = {
XS: 16,
S: 20,
M: 24,
L: 28,
XL: 32
};

function PhotoCard({item, layout}) {
return (
<Card id={item.id} textValue={item.description || item.alt_description}>
{({size}) => (<>
<CardPreview>
<Image
src={item.urls.regular}
styles={style({
width: 'full',
pointerEvents: 'none'
})}
// TODO - should we have a safe `dynamicStyles` or something for this?
UNSAFE_style={{
aspectRatio: layout === 'waterfall' ? `${item.width} / ${item.height}` : '4/3',
objectFit: layout === 'waterfall' ? 'contain' : 'cover'
}}
renderError={() => (
<div className={style({display: 'flex', alignItems: 'center', justifyContent: 'center', size: 'full'})}>
<ErrorIcon size="S" />
</div>
)} />
</CardPreview>
<Content>
<Text slot="title">{item.description || item.alt_description}</Text>
{size !== 'XS' && <ActionMenu>
<MenuItem>Test</MenuItem>
</ActionMenu>}
<div className={style({display: 'flex', alignItems: 'center', gap: 8, gridArea: 'description'})}>
<Avatar src={item.user.profile_image.small} size={avatarSize[size]} />
<Text slot="description">{item.user.name}</Text>
</div>
</Content>
</>)}
</Card>
);
}

export const CardViewExample = (props) => {
let list = useAsyncList({
async load({signal, cursor, items}) {
let page = cursor || 1;
let res = await fetch(
`https://api.unsplash.com/topics/nature/photos?page=${page}&per_page=30&client_id=AJuU-FPh11hn7RuumUllp4ppT8kgiLS7LtOHp_sp4nc`,
{signal}
);
let nextItems = await res.json();
// Filter duplicates which might be returned by the API.
let existingKeys = new Set(items.map(i => i.id));
nextItems = nextItems.filter(i => !existingKeys.has(i.id) && (i.description || i.alt_description));
return {items: nextItems, cursor: nextItems.length ? page + 1 : null};
}
});

let loadingState = props.loadingState === 'idle' ? list.loadingState : props.loadingState;
let items = loadingState === 'loading' ? [] : list.items;

return (
<CardView
aria-label="Nature photos"
{...props}
loadingState={loadingState}
onLoadMore={props.loadingState === 'idle' ? list.loadMore : undefined}
styles={cardViewStyles}>
<Collection items={items} dependencies={[props.layout]}>
{item => <PhotoCard item={item} layout={props.layout || 'grid'} />}
</Collection>
{(loadingState === 'loading' || loadingState === 'loadingMore') && (
<SkeletonCollection>
{() => (
<PhotoCard
item={{
id: Math.random(),
user: {name: 'Devon Govett', profile_image: {small: ''}},
urls: {regular: ''},
description: 'This is a fake description. Kinda long so it wraps to a new line.',
alt_description: '',
width: 400,
height: 200 + Math.max(0, Math.round(Math.random() * 400))
}}
layout={props.layout || 'grid'} />
)}
</SkeletonCollection>
)}
</CardView>
);
};

function TopicCard({topic}) {
return (
<Card href={topic.links.html} target="_blank" textValue={topic.title}>
<CollectionCardPreview>
{topic.preview_photos.slice(0, 4).map(photo => (
<Image key={photo.id} alt="" src={photo.urls.small} />
))}
</CollectionCardPreview>
<Content>
<Text slot="title">{topic.title}</Text>
<div className={style({display: 'flex', alignItems: 'center', gap: 8})}>
<Folder />
<Text slot="description">{topic.total_photos.toLocaleString()} photos</Text>
</div>
</Content>
</Card>
);
}

export const CollectionCardsExample = (props) => {
let list = useAsyncList({
async load({signal, cursor}) {
let page = cursor || 1;
let res = await fetch(
`https://api.unsplash.com/topics?page=${page}&per_page=30&client_id=AJuU-FPh11hn7RuumUllp4ppT8kgiLS7LtOHp_sp4nc`,
{signal}
);
let items = (await res.json()).filter((topic) => !!topic.preview_photos);
return {items, cursor: items.length ? page + 1 : null};
}
});

let loadingState = props.loadingState === 'idle' ? list.loadingState : props.loadingState;
let items = loadingState === 'loading' ? [] : list.items;

return (
<CardView
aria-label="Topics"
{...props}
loadingState={loadingState}
onLoadMore={props.loadingState === 'idle' ? list.loadMore : undefined}
styles={cardViewStyles}>
<Collection items={items}>
{topic => <TopicCard topic={topic} />}
</Collection>
{(loadingState === 'loading' || loadingState === 'loadingMore') && (
<SkeletonCollection>
{() => (
<TopicCard
topic={{
id: Math.random().toString(36),
title: 'Topic title',
total_photos: 80,
links: {html: ''},
preview_photos: [
{id: 'a', urls: {small: ''}},
{id: 'b', urls: {small: ''}},
{id: 'c', urls: {small: ''}},
{id: 'd', urls: {small: ''}}
]
}} />
)}
</SkeletonCollection>
)}
</CardView>
);
};
Loading