Skip to content

Commit c3efc31

Browse files
committed
More cleanup and docs
1 parent 13d7b64 commit c3efc31

File tree

6 files changed

+100
-32
lines changed

6 files changed

+100
-32
lines changed

packages/@react-spectrum/s2/src/Image.tsx

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,43 @@ import {useLayoutEffect} from '@react-aria/utils';
1010
import {useSpectrumContextProps} from './useSpectrumContextProps';
1111

1212
export interface ImageProps extends UnsafeStyles, SlotProps {
13+
/** The URL of the image. */
1314
src?: string,
1415
// TODO
1516
// srcSet?: string,
1617
// sizes?: string,
18+
/** Accessible alt text for the image. */
1719
alt?: string,
20+
/**
21+
* Indicates if the fetching of the image must be done using a CORS request.
22+
* [See MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin).
23+
*/
1824
crossOrigin?: 'anonymous' | 'use-credentials',
25+
/**
26+
* Whether the browser should decode images synchronously or asynchronously.
27+
* [See MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#decoding).
28+
*/
1929
decoding?: 'async' | 'auto' | 'sync',
2030
// Only supported in React 19...
2131
// fetchPriority?: 'high' | 'low' | 'auto',
32+
/**
33+
* Whether the image should be loaded immediately or lazily when scrolled into view.
34+
* [See MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#loading).
35+
*/
2236
loading?: 'eager' | 'lazy',
37+
/**
38+
* A string indicating which referrer to use when fetching the resource.
39+
* [See MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#referrerpolicy).
40+
*/
2341
referrerPolicy?: HTMLAttributeReferrerPolicy,
42+
/** Spectrum-defined styles, returned by the `style()` macro. */
2443
styles?: StyleString,
44+
/** A function that is called to render a fallback when the image fails to load. */
2545
renderError?: () => ReactNode,
46+
/**
47+
* A group of images to coordinate between, matching the group passed to the `<ImageCoordinator>` component.
48+
* If not provided, the default image group is used.
49+
*/
2650
group?: ImageGroup
2751
}
2852

@@ -84,11 +108,28 @@ function reducer(state: State, action: Action): State {
84108
}
85109
}
86110

87-
const imageStyles = style({
111+
const wrapperStyles = style({
88112
backgroundColor: 'gray-100',
89113
overflow: 'hidden'
90114
});
91115

116+
const imgStyles = style({
117+
display: 'block',
118+
width: 'full',
119+
height: 'full',
120+
objectFit: '[inherit]',
121+
objectPosition: '[inherit]',
122+
opacity: {
123+
default: 0,
124+
isRevealed: 1
125+
},
126+
transition: {
127+
default: 'none',
128+
isTransitioning: 'opacity'
129+
},
130+
transitionDuration: 500
131+
});
132+
92133
function Image(props: ImageProps, domRef: ForwardedRef<HTMLDivElement>) {
93134
[props, domRef] = useSpectrumContextProps(props, domRef, ImageContext);
94135

@@ -171,12 +212,12 @@ function Image(props: ImageProps, domRef: ForwardedRef<HTMLDivElement>) {
171212

172213
let errorState = !isSkeleton && state === 'error' && renderError?.();
173214
let isRevealed = state === 'revealed' && !isSkeleton;
174-
let transition = isRevealed && loadTime > 200 ? 'opacity 500ms' : undefined;
215+
let isTransitioning = isRevealed && loadTime > 200;
175216
return useMemo(() => hidden ? null : (
176217
<div
177218
ref={domRef}
178219
style={UNSAFE_style}
179-
className={UNSAFE_className + mergeStyles(imageStyles, styles) + ' ' + (isAnimating ? loadingStyle : '')}>
220+
className={UNSAFE_className + mergeStyles(wrapperStyles, styles) + ' ' + (isAnimating ? loadingStyle : '')}>
180221
{errorState}
181222
{!errorState && (
182223
<img
@@ -189,18 +230,10 @@ function Image(props: ImageProps, domRef: ForwardedRef<HTMLDivElement>) {
189230
ref={imgRef}
190231
onLoad={onLoad}
191232
onError={onError}
192-
style={{
193-
display: 'block',
194-
width: '100%',
195-
height: '100%',
196-
objectFit: 'inherit',
197-
objectPosition: 'inherit',
198-
opacity: isRevealed ? 1 : 0,
199-
transition
200-
}} />
233+
className={imgStyles({isRevealed, isTransitioning})} />
201234
)}
202235
</div>
203-
), [hidden, domRef, UNSAFE_style, UNSAFE_className, styles, isAnimating, errorState, src, alt, crossOrigin, decoding, loading, referrerPolicy, onLoad, onError, isRevealed, transition]);
236+
), [hidden, domRef, UNSAFE_style, UNSAFE_className, styles, isAnimating, errorState, src, alt, crossOrigin, decoding, loading, referrerPolicy, onLoad, onError, isRevealed, isTransitioning]);
204237
}
205238

206239
const _Image = forwardRef(Image);

packages/@react-spectrum/s2/src/ImageCoordinator.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import {Context, createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useReducer} from 'react';
22

33
export interface ImageCoordinatorProps {
4+
/** Children within the ImageCoordinator. */
45
children: ReactNode,
6+
/**
7+
* Time in milliseconds after which images are always displayed, even if all images are not yet loaded.
8+
* @default 5000
9+
*/
510
timeout?: number,
11+
/**
12+
* A group of images to coordinate between, matching the group passed to the `<Image>` component.
13+
* If not provided, the default image group is used.
14+
*/
615
group?: ImageGroup
716
}
817

@@ -107,6 +116,10 @@ function isAllLoaded(loaded: Map<string, boolean>) {
107116
return true;
108117
}
109118

119+
/**
120+
* An ImageCoordinator coordinates loading behavior for a group of images.
121+
* Images within an ImageCoordinator are revealed together once all of them have loaded.
122+
*/
110123
export function ImageCoordinator(props: ImageCoordinatorProps) {
111124
// If we are already inside another ImageCoordinator, just pass
112125
// through children and coordinate loading at the root.

packages/@react-spectrum/s2/src/style-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ const heightProperties = [
188188
] as const;
189189

190190
export type StylesProp = StyleString<(typeof allowedOverrides)[number] | (typeof widthProperties)[number]>;
191-
export type StylesPropWithHeight = StyleString<(typeof allowedOverrides)[number] | (typeof heightProperties)[number]>;
191+
export type StylesPropWithHeight = StyleString<(typeof allowedOverrides)[number] | (typeof widthProperties)[number] | (typeof heightProperties)[number]>;
192192
export type StylesPropWithoutWidth = StyleString<(typeof allowedOverrides)[number]>;
193193
export interface UnsafeStyles {
194194
/** Sets the CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. Only use as a **last resort**. Use the `style` macro via the `styles` prop instead. */

packages/@react-spectrum/s2/stories/CardView.stories.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ const meta: Meta<typeof CardView> = {
2828

2929
export default meta;
3030

31+
const cardViewStyles = style({
32+
width: {
33+
default: 'screen',
34+
viewMode: {
35+
docs: 'full'
36+
}
37+
},
38+
height: {
39+
default: 'screen',
40+
viewMode: {
41+
docs: '[600px]'
42+
}
43+
}
44+
});
45+
3146
type Item = {
3247
id: number,
3348
user: {
@@ -51,6 +66,7 @@ function PhotoCard({item, layout}: {item: Item, layout: string}) {
5166
width: 'full',
5267
pointerEvents: 'none'
5368
})}
69+
// TODO - should we have a safe `dynamicStyles` or something for this?
5470
UNSAFE_style={{
5571
aspectRatio: layout === 'waterfall' ? `${item.width} / ${item.height}` : '4/3',
5672
objectFit: layout === 'waterfall' ? 'contain' : 'cover'
@@ -96,11 +112,11 @@ export const Example = (args: CardViewProps<any>, {viewMode}) => {
96112

97113
return (
98114
<CardView
99-
aria-label="Assets"
115+
aria-label="Nature photos"
100116
{...args}
101117
loadingState={loadingState}
102118
onLoadMore={args.loadingState === 'idle' ? list.loadMore : undefined}
103-
UNSAFE_style={{height: viewMode === 'docs' ? 600 : '100vh', width: viewMode === 'docs' ? '100%' : '100vw'}}>
119+
styles={cardViewStyles({viewMode})}>
104120
<Collection items={items} dependencies={[args.layout]}>
105121
{item => <PhotoCard item={item} layout={args.layout || 'grid'} />}
106122
</Collection>
@@ -136,7 +152,7 @@ export const Empty = (args: CardViewProps<any>, {viewMode}) => {
136152
<CardView
137153
aria-label="Assets"
138154
{...args}
139-
UNSAFE_style={{height: viewMode === 'docs' ? 600 : '100vh', width: viewMode === 'docs' ? '100%' : '100vw'}}
155+
styles={cardViewStyles({viewMode})}
140156
renderEmptyState={() => (
141157
<IllustratedMessage size="L">
142158
<EmptyIcon />
@@ -194,12 +210,12 @@ export const CollectionCards = (args: CardViewProps<any>, {viewMode}) => {
194210

195211
return (
196212
<CardView
197-
aria-label="Assets"
213+
aria-label="Topics"
198214
{...args}
199215
loadingState={loadingState}
200216
onLoadMore={args.loadingState === 'idle' ? list.loadMore : undefined}
201-
UNSAFE_style={{height: viewMode === 'docs' ? 600 : '100vh', width: viewMode === 'docs' ? '100%' : '100vw'}}>
202-
<Collection items={items} dependencies={[args.layout]}>
217+
styles={cardViewStyles({viewMode})}>
218+
<Collection items={items}>
203219
{topic => <TopicCard topic={topic} />}
204220
</Collection>
205221
{(loadingState === 'loading' || loadingState === 'loadingMore') && (

packages/@react-spectrum/s2/style/spectrum-theme.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ const sizing = {
215215
...scaledSpacing,
216216
auto: 'auto',
217217
full: '100%',
218-
screen: '100vh',
219218
min: 'min-content',
220219
max: 'max-content',
221220
fit: 'fit-content',
@@ -241,6 +240,16 @@ const sizing = {
241240
}
242241
};
243242

243+
const height = {
244+
...sizing,
245+
screen: '100vh'
246+
};
247+
248+
const width = {
249+
...sizing,
250+
screen: '100vw'
251+
};
252+
244253
const margin = {
245254
...spacing,
246255
...negativeSpacing,
@@ -576,18 +585,18 @@ export const style = createTheme({
576585
},
577586
rowGap: spacing,
578587
columnGap: spacing,
579-
height: sizing,
580-
width: sizing,
581-
containIntrinsicWidth: sizing,
582-
containIntrinsicHeight: sizing,
583-
minHeight: sizing,
588+
height,
589+
width,
590+
containIntrinsicWidth: width,
591+
containIntrinsicHeight: height,
592+
minHeight: height,
584593
maxHeight: {
585-
...sizing,
594+
...height,
586595
none: 'none'
587596
},
588-
minWidth: sizing,
597+
minWidth: width,
589598
maxWidth: {
590-
...sizing,
599+
...width,
591600
none: 'none'
592601
},
593602
borderStartWidth: createRenamedProperty('borderInlineStartWidth', borderWidth),

packages/@react-spectrum/s2/style/style-macro.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,6 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
224224
for (let property of allowedOverrides) {
225225
if (themePropertyMap.has(property as string)) {
226226
allowedOverridesSet.add(themePropertyMap.get(property as string)!);
227-
// Merge custom properties for self() references too.
228-
// TODO: optimize this
229-
allowedOverridesSet.add(generateArbitraryValueSelector(`--${themePropertyMap.get(property)}`, true) + '_' + themePropertyMap.get(property as string)!);
230227
}
231228
}
232229

0 commit comments

Comments
 (0)