Skip to content

Commit dcbb9dc

Browse files
devongovettLFDanLu
andauthored
Improve shift selection and responsive layout in S2 CardView (#7030)
* Allow layout delegate to override shift selection behavior * Fix virtualizer unmounting and remounting when changing layout options * Automatically reduce card size based on available space * Fix TS strict --------- Co-authored-by: Daniel Lu <[email protected]>
1 parent 9da4651 commit dcbb9dc

File tree

9 files changed

+390
-299
lines changed

9 files changed

+390
-299
lines changed

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

Lines changed: 143 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import {AvatarContext} from './Avatar';
1515
import {ButtonContext, LinkButtonContext} from './Button';
1616
import {Checkbox} from './Checkbox';
1717
import {colorToken} from '../style/tokens' with {type: 'macro'};
18+
import {composeRenderProps, ContextValue, DEFAULT_SLOT, type GridListItem, GridListItemProps, Provider} from 'react-aria-components';
1819
import {ContentContext, FooterContext, TextContext} from './Content';
19-
import {ContextValue, DEFAULT_SLOT, type GridListItem, GridListItemProps, Provider} from 'react-aria-components';
2020
import {createContext, CSSProperties, forwardRef, ReactNode, useContext} from 'react';
2121
import {DividerContext} from './Divider';
2222
import {DOMProps, DOMRef, DOMRefValue} from '@react-types/shared';
@@ -32,9 +32,14 @@ import {SkeletonContext, SkeletonWrapper, useIsSkeleton} from './Skeleton';
3232
import {useDOMRef} from '@react-spectrum/utils';
3333
import {useSpectrumContextProps} from './useSpectrumContextProps';
3434

35+
interface CardRenderProps {
36+
/** The size of the Card. */
37+
size: 'XS' | 'S' | 'M' | 'L' | 'XL'
38+
}
39+
3540
export interface CardProps extends Omit<GridListItemProps, 'className' | 'style' | 'children'>, StyleProps {
3641
/** The children of the Card. */
37-
children: ReactNode,
42+
children: ReactNode | ((renderProps: CardRenderProps) => ReactNode),
3843
/**
3944
* The size of the Card.
4045
* @default 'M'
@@ -396,7 +401,7 @@ export const Card = forwardRef(function Card(props: CardProps, ref: DOMRef<HTMLD
396401
[SkeletonContext, isSkeleton]
397402
]}>
398403
<ImageCoordinator>
399-
{props.children}
404+
{typeof props.children === 'function' ? props.children({size}) : props.children}
400405
</ImageCoordinator>
401406
</Provider>
402407
);
@@ -550,45 +555,47 @@ export interface AssetCardProps extends Omit<CardProps, 'density'> {}
550555
export const AssetCard = forwardRef(function AssetCard(props: AssetCardProps, ref: DOMRef<HTMLDivElement>) {
551556
return (
552557
<Card {...props} ref={ref} density="regular">
553-
<Provider
554-
values={[
555-
[ImageContext, {
556-
alt: '',
557-
styles: style({
558-
width: 'full',
559-
aspectRatio: 'square',
560-
objectFit: 'contain',
561-
pointerEvents: 'none',
562-
userSelect: 'none'
563-
})
564-
}],
565-
[IllustrationContext, {
566-
render(icon) {
567-
return (
568-
<SkeletonWrapper>
569-
<div
570-
className={style({
571-
display: 'flex',
572-
alignItems: 'center',
573-
justifyContent: 'center',
574-
backgroundColor: 'gray-100',
575-
aspectRatio: 'square'
576-
})}>
577-
{icon}
578-
</div>
579-
</SkeletonWrapper>
580-
);
581-
},
582-
styles: style({
583-
height: 'auto',
584-
maxSize: 160,
585-
// TODO: this is made up.
586-
width: '[50%]'
587-
})
588-
}]
589-
]}>
590-
{props.children}
591-
</Provider>
558+
{composeRenderProps(props.children, children => (
559+
<Provider
560+
values={[
561+
[ImageContext, {
562+
alt: '',
563+
styles: style({
564+
width: 'full',
565+
aspectRatio: 'square',
566+
objectFit: 'contain',
567+
pointerEvents: 'none',
568+
userSelect: 'none'
569+
})
570+
}],
571+
[IllustrationContext, {
572+
render(icon) {
573+
return (
574+
<SkeletonWrapper>
575+
<div
576+
className={style({
577+
display: 'flex',
578+
alignItems: 'center',
579+
justifyContent: 'center',
580+
backgroundColor: 'gray-100',
581+
aspectRatio: 'square'
582+
})}>
583+
{icon}
584+
</div>
585+
</SkeletonWrapper>
586+
);
587+
},
588+
styles: style({
589+
height: 'auto',
590+
maxSize: 160,
591+
// TODO: this is made up.
592+
width: '[50%]'
593+
})
594+
}]
595+
]}>
596+
{children}
597+
</Provider>
598+
))}
592599
</Card>
593600
);
594601
});
@@ -610,35 +617,37 @@ export const UserCard = forwardRef(function UserCard(props: CardProps, ref: DOMR
610617
let {size = 'M'} = props;
611618
return (
612619
<Card {...props} ref={ref} density="spacious">
613-
<Provider
614-
values={[
615-
[ImageContext, {
616-
alt: '',
617-
styles: style({
618-
width: 'full',
619-
aspectRatio: '[3/1]',
620-
objectFit: 'cover',
621-
pointerEvents: 'none',
622-
userSelect: 'none'
623-
})
624-
}],
625-
[AvatarContext, {
626-
size: avatarSize[size],
627-
UNSAFE_style: {
628-
'--size': avatarSize[size] + 'px'
629-
} as CSSProperties,
630-
styles: style({
631-
position: 'relative',
632-
marginTop: {
633-
default: 0,
634-
':is([slot=preview] + &)': '[calc(var(--size) / -2)]'
635-
}
636-
}),
637-
isOverBackground: true
638-
}]
639-
]}>
640-
{props.children}
641-
</Provider>
620+
{composeRenderProps(props.children, children => (
621+
<Provider
622+
values={[
623+
[ImageContext, {
624+
alt: '',
625+
styles: style({
626+
width: 'full',
627+
aspectRatio: '[3/1]',
628+
objectFit: 'cover',
629+
pointerEvents: 'none',
630+
userSelect: 'none'
631+
})
632+
}],
633+
[AvatarContext, {
634+
size: avatarSize[size],
635+
UNSAFE_style: {
636+
'--size': avatarSize[size] + 'px'
637+
} as CSSProperties,
638+
styles: style({
639+
position: 'relative',
640+
marginTop: {
641+
default: 0,
642+
':is([slot=preview] + &)': '[calc(var(--size) / -2)]'
643+
}
644+
}),
645+
isOverBackground: true
646+
}]
647+
]}>
648+
{children}
649+
</Provider>
650+
))}
642651
</Card>
643652
);
644653
});
@@ -660,69 +669,71 @@ export const ProductCard = forwardRef(function ProductCard(props: ProductCardPro
660669
let {size = 'M'} = props;
661670
return (
662671
<Card {...props} ref={ref} density="spacious">
663-
<Provider
664-
values={[
665-
[ImageContext, {
666-
slots: {
667-
preview: {
668-
alt: '',
669-
styles: style({
670-
width: 'full',
671-
aspectRatio: '[5/1]',
672-
objectFit: 'cover',
673-
pointerEvents: 'none',
674-
userSelect: 'none'
675-
})
676-
},
677-
thumbnail: {
678-
alt: '',
679-
styles: style({
680-
position: 'relative',
681-
pointerEvents: 'none',
682-
userSelect: 'none',
683-
size: {
684-
size: {
685-
XS: 24,
686-
S: 36,
687-
M: 40,
688-
L: 44,
689-
XL: 56
690-
}
691-
},
692-
borderRadius: {
693-
default: 'default',
672+
{composeRenderProps(props.children, children => (
673+
<Provider
674+
values={[
675+
[ImageContext, {
676+
slots: {
677+
preview: {
678+
alt: '',
679+
styles: style({
680+
width: 'full',
681+
aspectRatio: '[5/1]',
682+
objectFit: 'cover',
683+
pointerEvents: 'none',
684+
userSelect: 'none'
685+
})
686+
},
687+
thumbnail: {
688+
alt: '',
689+
styles: style({
690+
position: 'relative',
691+
pointerEvents: 'none',
692+
userSelect: 'none',
694693
size: {
695-
XS: 'sm',
696-
S: 'sm'
697-
}
698-
},
699-
objectFit: 'cover',
700-
marginTop: {
701-
default: 0,
702-
':is([slot=preview] + &)': '[calc(self(height) / -2)]'
703-
},
704-
outlineStyle: 'solid',
705-
outlineWidth: {
706-
default: 2,
707-
size: {
708-
XS: 1
709-
}
710-
},
711-
outlineColor: '--s2-container-bg'
712-
})({size})
694+
size: {
695+
XS: 24,
696+
S: 36,
697+
M: 40,
698+
L: 44,
699+
XL: 56
700+
}
701+
},
702+
borderRadius: {
703+
default: 'default',
704+
size: {
705+
XS: 'sm',
706+
S: 'sm'
707+
}
708+
},
709+
objectFit: 'cover',
710+
marginTop: {
711+
default: 0,
712+
':is([slot=preview] + &)': '[calc(self(height) / -2)]'
713+
},
714+
outlineStyle: 'solid',
715+
outlineWidth: {
716+
default: 2,
717+
size: {
718+
XS: 1
719+
}
720+
},
721+
outlineColor: '--s2-container-bg'
722+
})({size})
723+
}
713724
}
714-
}
715-
}],
716-
[FooterContext, {
717-
styles: mergeStyles(footer, style({
718-
justifyContent: 'end'
719-
}))
720-
}],
721-
[ButtonContext, {size: buttonSize[size]}],
722-
[LinkButtonContext, {size: buttonSize[size]}]
723-
]}>
724-
{props.children}
725-
</Provider>
725+
}],
726+
[FooterContext, {
727+
styles: mergeStyles(footer, style({
728+
justifyContent: 'end'
729+
}))
730+
}],
731+
[ButtonContext, {size: buttonSize[size]}],
732+
[LinkButtonContext, {size: buttonSize[size]}]
733+
]}>
734+
{children}
735+
</Provider>
736+
))}
726737
</Card>
727738
);
728739
});

0 commit comments

Comments
 (0)