Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

S2 CardView and ghost loading #6978

Merged
merged 24 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0468889
Add S2 Illustrations
devongovett Aug 8, 2024
3710152
Fix storybook
devongovett Aug 8, 2024
aa61cba
fix name
devongovett Aug 8, 2024
28f6a86
S2 cards
devongovett Aug 7, 2024
660e768
Add slot contexts to all S2 components
devongovett Aug 9, 2024
20efb05
Merge branch 's2-contexts' of github.com:adobe/react-spectrum into s2…
devongovett Aug 9, 2024
f510229
separate button and menu size in ActionMenu
devongovett Aug 9, 2024
bb848fc
Support illustrations in cards and add auto sizing for action menu an…
devongovett Aug 9, 2024
337a562
Add skeleton loading states
devongovett Aug 13, 2024
e5c307a
Merge branch 'main' of github.com:adobe/react-spectrum into s2-card
devongovett Aug 26, 2024
9ce8efa
Fix merge
devongovett Aug 26, 2024
8ec3f04
Add SkeletonCollection, improve waterfall keyboard navigation, clean up
devongovett Aug 30, 2024
c1487ac
Fixes and cleanup
devongovett Aug 30, 2024
62d17f3
Review comments
devongovett Sep 9, 2024
6a4ee24
Merge branch 'main' into s2-card
devongovett Sep 9, 2024
30f45ef
fix background
devongovett Sep 9, 2024
d28ec8a
Merge branch 's2-card' of github.com:adobe/react-spectrum into s2-card
devongovett Sep 9, 2024
179f36c
Fix RTL keyboard navigation
devongovett Sep 9, 2024
f79182a
Update layout when content size changes might cause scrollbars to app…
devongovett Sep 10, 2024
7f57237
Fix React < 18 tests
devongovett Sep 10, 2024
d412c67
Always update whenever content size changes
devongovett Sep 10, 2024
13d7b64
Add prop docs
devongovett Sep 10, 2024
c3efc31
More cleanup and docs
devongovett Sep 11, 2024
1bff41b
Design updates
devongovett Sep 11, 2024
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
Prev Previous commit
Design updates
  • Loading branch information
devongovett committed Sep 11, 2024
commit 1bff41bfe9e8db8afc360387748e0bb475639857
135 changes: 86 additions & 49 deletions packages/@react-spectrum/s2/src/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {ImageContext} from './Image';
import {ImageCoordinator} from './ImageCoordinator';
import {lightDark, size, style} from '../style/spectrum-theme' with {type: 'macro'};
import {mergeStyles} from '../style/runtime';
import {PressResponder} from '@react-aria/interactions';
import {pressScale} from './pressScale';
import {SkeletonContext, SkeletonWrapper, useIsSkeleton} from './Skeleton';
import {useDOMRef} from '@react-spectrum/utils';
Expand Down Expand Up @@ -135,7 +134,7 @@ let card = style({
compact: {
size: {
XS: size(6),
S: size(10),
S: 8,
M: 12,
L: 16,
XL: 20
Expand Down Expand Up @@ -207,11 +206,16 @@ let selectionIndicator = style({
opacity: {
default: 0,
isSelected: 1
}
// outlineColor: 'white',
// outlineOffset: -4,
// outlineStyle: 'solid',
// outlineWidth: 2
},
// Quiet cards with no checkbox have an extra inner stroke
// to distinguish the selection indicator from the preview.
outlineColor: 'gray-25',
outlineOffset: -4,
outlineStyle: {
default: 'none',
isStrokeInner: 'solid'
},
outlineWidth: 2
});

let preview = style({
Expand Down Expand Up @@ -261,7 +265,8 @@ let title = style({
XL: 'title-lg'
}
},
lineClamp: 3
lineClamp: 3,
gridArea: 'title'
});

let description = style({
Expand All @@ -276,23 +281,38 @@ let description = style({
}
},
lineClamp: 3,
gridColumnEnd: 'span 2'
gridArea: 'description'
});

let content = style({
display: 'grid',
// By default, all elements are displayed in a stack.
// If an action menu is present, place it next to the title.
gridTemplateColumns: {
default: ['1fr'],
':has([data-slot=menu])': ['minmax(0, 1fr)', 'auto']
},
gridTemplateAreas: {
default: [
'title',
'description'
],
':has([data-slot=menu])': [
'title menu',
'description description'
]
},
columnGap: 4,
flexGrow: 1,
alignItems: 'baseline',
alignContent: 'space-between',
rowGap: {
default: 8,
size: {
XS: 4
XS: 4,
S: 4,
M: size(6),
L: size(6),
XL: 8
}
},
paddingTop: {
Expand Down Expand Up @@ -322,15 +342,19 @@ interface InternalCardContextValue {
size: 'XS' | 'S' | 'M' | 'L' | 'XL',
isSelected: boolean,
isHovered: boolean,
isFocusVisible: boolean
isFocusVisible: boolean,
isPressed: boolean,
isCheckboxSelection: boolean
}

const InternalCardContext = createContext<InternalCardContextValue>({
isQuiet: false,
size: 'M',
isSelected: false,
isHovered: false,
isFocusVisible: false
isFocusVisible: false,
isPressed: false,
isCheckboxSelection: true
});

const actionButtonSize = {
Expand Down Expand Up @@ -366,7 +390,8 @@ export const Card = forwardRef(function Card(props: CardProps, ref: DOMRef<HTMLD
size: actionButtonSize[size],
isDisabled: isSkeleton,
// @ts-ignore
'data-slot': 'menu'
'data-slot': 'menu',
styles: style({gridArea: 'menu'})
}],
[SkeletonContext, isSkeleton]
]}>
Expand All @@ -387,7 +412,7 @@ export const Card = forwardRef(function Card(props: CardProps, ref: DOMRef<HTMLD
ref={domRef}
className={UNSAFE_className + card({size, density, variant, isCardView: ElementType !== 'div'}, styles)}
style={UNSAFE_style}>
<InternalCardContext.Provider value={{size, isQuiet, isHovered: false, isFocusVisible: false, isSelected: false}}>
<InternalCardContext.Provider value={{size, isQuiet, isCheckboxSelection: false, isHovered: false, isFocusVisible: false, isSelected: false, isPressed: false}}>
{children}
</InternalCardContext.Provider>
</div>
Expand All @@ -401,36 +426,16 @@ export const Card = forwardRef(function Card(props: CardProps, ref: DOMRef<HTMLD
ref={domRef}
className={renderProps => UNSAFE_className + card({...renderProps, isCardView: true, isLink: !!props.href, size, density, variant}, styles)}
style={renderProps =>
// Only apply press scaling to card when it has an action, not selection.
// When clicking the card selects it, the checkbox will scale down instead.
// TODO: confirm with design
// @ts-ignore - do we want to expose hasAction publically in RAC?
renderProps.hasAction && (renderProps.selectionMode === 'none' || renderProps.selectionBehavior === 'toggle')
? press(renderProps)
: UNSAFE_style
// Only the preview in quiet cards scales down on press
variant === 'quiet' ? UNSAFE_style : press(renderProps)
}>
{({selectionMode, selectionBehavior, isHovered, isFocusVisible, isSelected, isPressed}) => (
<InternalCardContext.Provider value={{size, isQuiet, isHovered, isFocusVisible, isSelected}}>
<InternalCardContext.Provider value={{size, isQuiet, isCheckboxSelection: selectionMode !== 'none' && selectionBehavior === 'toggle', isHovered, isFocusVisible, isSelected, isPressed}}>
{/* Selection indicator and checkbox move inside the preview for quiet cards */}
{!isQuiet && <SelectionIndicator />}
{selectionMode !== 'none' && selectionBehavior === 'toggle' && (
<PressResponder isPressed={isPressed}>
<div
className={style({
position: 'absolute',
top: 8,
zIndex: 2,
insetStart: 8,
padding: '[6px]',
backgroundColor: lightDark('transparent-white-600', 'transparent-black-600'),
borderRadius: 'default',
boxShadow: 'emphasized'
})}>
<Checkbox
slot="selection"
excludeFromTabOrder />
</div>
</PressResponder>
)}
{!isQuiet && selectionMode !== 'none' && selectionBehavior === 'toggle' &&
<CardCheckbox />
}
{/* this makes the :first-child selector work even with the checkbox */}
<div className={style({display: 'contents'})}>
{children}
Expand All @@ -442,16 +447,47 @@ export const Card = forwardRef(function Card(props: CardProps, ref: DOMRef<HTMLD
});

function SelectionIndicator() {
let {isSelected} = useContext(InternalCardContext);
return <div className={selectionIndicator({isSelected})} />;
let {size, isSelected, isQuiet, isCheckboxSelection} = useContext(InternalCardContext);
return (
<div
className={selectionIndicator({
size,
isSelected,
// Add an inner stroke only for quiet cards with no checkbox to
// help distinguish the selected state from the preview.
isStrokeInner: isQuiet && !isCheckboxSelection
})} />
);
}

function CardCheckbox() {
let {size} = useContext(InternalCardContext);
return (
<div
className={style({
position: 'absolute',
top: '--card-spacing',
insetStart: '--card-spacing',
zIndex: 2,
padding: 4,
backgroundColor: lightDark('transparent-white-600', 'transparent-black-600'),
borderRadius: 'default',
boxShadow: 'emphasized'
})}>
<Checkbox
slot="selection"
excludeFromTabOrder
size={size === 'XS' ? 'S' : size} />
</div>
);
}

export interface CardPreviewProps extends UnsafeStyles, DOMProps {
children: ReactNode
}

export const CardPreview = forwardRef(function CardPreview(props: CardPreviewProps, ref: DOMRef<HTMLDivElement>) {
let {size, isQuiet, isHovered, isFocusVisible, isSelected} = useContext(InternalCardContext);
let {size, isQuiet, isHovered, isFocusVisible, isSelected, isPressed, isCheckboxSelection} = useContext(InternalCardContext);
let {UNSAFE_className, UNSAFE_style} = props;
let domRef = useDOMRef(ref);
return (
Expand All @@ -460,8 +496,9 @@ export const CardPreview = forwardRef(function CardPreview(props: CardPreviewPro
slot="preview"
ref={domRef}
className={UNSAFE_className + preview({size, isQuiet, isHovered, isFocusVisible, isSelected})}
style={UNSAFE_style}>
style={isQuiet ? pressScale(domRef)({isPressed}) : UNSAFE_style}>
{isQuiet && <SelectionIndicator />}
{isQuiet && isCheckboxSelection && <CardCheckbox />}
<div className={style({borderRadius: '[inherit]', overflow: 'clip'})}>
{props.children}
</div>
Expand Down Expand Up @@ -608,10 +645,10 @@ export const UserCard = forwardRef(function UserCard(props: CardProps, ref: DOMR

const buttonSize = {
XS: 'S',
S: 'M',
S: 'S',
M: 'M',
L: 'M',
XL: 'L'
L: 'L',
XL: 'XL'
} as const;

export interface ProductCardProps extends Omit<CardProps, 'density' | 'variant'> {
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/stories/CardView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function PhotoCard({item, layout}: {item: Item, layout: string}) {
<ActionMenu>
<MenuItem>Test</MenuItem>
</ActionMenu>
<div className={style({display: 'flex', alignItems: 'center', gap: 8, gridColumnEnd: 'span 2'})}>
<div className={style({display: 'flex', alignItems: 'center', gap: 8, gridArea: 'description'})}>
<Avatar src={item.user.profile_image.small} />
<Text slot="description">{item.user.name}</Text>
</div>
Expand Down Expand Up @@ -183,7 +183,7 @@ function TopicCard({topic}: {topic: Topic}) {
</CollectionCardPreview>
<Content>
<Text slot="title">{topic.title}</Text>
<div className={style({gridColumnEnd: 'span 2', display: 'flex', alignItems: 'center', gap: 8})}>
<div className={style({display: 'flex', alignItems: 'center', gap: 8})}>
<Folder />
<Text slot="description">{topic.total_photos.toLocaleString()} photos</Text>
</div>
Expand Down