Skip to content

Commit ba0dfca

Browse files
committed
chore: refactor ad reuse card component for articles
1 parent 016333f commit ba0dfca

File tree

12 files changed

+239
-253
lines changed

12 files changed

+239
-253
lines changed

src/app/Components/ArticleCard.tsx

+12-71
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
import {
2-
Flex,
3-
Image,
4-
SkeletonBox,
5-
SkeletonText,
6-
Spacer,
7-
Text,
8-
useTheme,
9-
} from "@artsy/palette-mobile"
101
import { ArticleCard_article$data } from "__generated__/ArticleCard_article.graphql"
11-
import { RouterLink } from "app/system/navigation/RouterLink"
2+
import { CardWithMetaData } from "app/Components/Cards/CardWithMetaData"
123
import { compact } from "lodash"
134
import { DateTime } from "luxon"
14-
import { GestureResponderEvent, useWindowDimensions, View, ViewProps } from "react-native"
5+
import { GestureResponderEvent, ViewProps } from "react-native"
156
import { createFragmentContainer, graphql } from "react-relay"
167

178
export const ARTICLE_CARD_IMAGE_WIDTH = 295
@@ -26,55 +17,22 @@ interface ArticleCardProps extends ViewProps {
2617
export const ArticleCard: React.FC<ArticleCardProps> = ({ article, onPress, isFluid }) => {
2718
const imageURL = article.thumbnailImage?.url
2819

29-
const { space } = useTheme()
30-
const { width } = useWindowDimensions()
31-
3220
const formattedPublishedAt =
3321
article.publishedAt && DateTime.fromISO(article.publishedAt).toFormat("MMM d, yyyy")
3422

3523
const formattedVerticalAndDate = compact([article.vertical, formattedPublishedAt]).join(" • ")
3624

3725
return (
38-
<Flex width={isFluid ? "100%" : ARTICLE_CARD_IMAGE_WIDTH}>
39-
<RouterLink onPress={onPress} testID="article-card" to={article.href}>
40-
<Flex width={isFluid ? "100%" : ARTICLE_CARD_IMAGE_WIDTH} overflow="hidden">
41-
{!!imageURL &&
42-
(isFluid ? (
43-
<>
44-
<View style={{ width }}>
45-
<Image
46-
src={imageURL}
47-
// aspect ratio is fixed to 1.33 to match the old image aspect ratio
48-
aspectRatio={1.33}
49-
// 40 here comes from the mx={2} from the parent component
50-
width={width - 2 * space(2)}
51-
/>
52-
</View>
53-
</>
54-
) : (
55-
<Image
56-
src={imageURL}
57-
width={ARTICLE_CARD_IMAGE_WIDTH}
58-
height={ARTICLE_CARD_IMAGE_HEIGHT}
59-
/>
60-
))}
61-
<Spacer y={1} />
62-
<Text numberOfLines={2} ellipsizeMode="tail" variant="sm-display" mb={0.5}>
63-
{article.thumbnailTitle}
64-
</Text>
65-
{!!article.byline && (
66-
<Text color="black60" variant="xs">
67-
{article.byline}
68-
</Text>
69-
)}
70-
{!!article.publishedAt && (
71-
<Text color="black100" variant="xs">
72-
{formattedVerticalAndDate}
73-
</Text>
74-
)}
75-
</Flex>
76-
</RouterLink>
77-
</Flex>
26+
<CardWithMetaData
27+
testId="article-card"
28+
isFluid={isFluid}
29+
href={article.href}
30+
imageURL={imageURL}
31+
title={article.thumbnailTitle}
32+
subtitle={article.byline}
33+
tag={formattedVerticalAndDate}
34+
onPress={onPress}
35+
/>
7836
)
7937
}
8038

@@ -94,20 +52,3 @@ export const ArticleCardContainer = createFragmentContainer(ArticleCard, {
9452
}
9553
`,
9654
})
97-
98-
export const SkeletonArticleCard: React.FC = () => (
99-
<Flex maxWidth={ARTICLE_CARD_IMAGE_WIDTH}>
100-
<SkeletonBox height={ARTICLE_CARD_IMAGE_HEIGHT} width={ARTICLE_CARD_IMAGE_WIDTH} />
101-
<Spacer y={1} />
102-
<SkeletonText variant="lg-display" mb={0.5}>
103-
10 Shows we suggest you don't miss during Berlin Art Week
104-
</SkeletonText>
105-
106-
<SkeletonText variant="xs" numberOfLines={1}>
107-
Article Author
108-
</SkeletonText>
109-
<SkeletonText variant="xs" numberOfLines={1}>
110-
Art • Sep 10, 2024
111-
</SkeletonText>
112-
</Flex>
113-
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import {
2+
Flex,
3+
Image,
4+
SkeletonBox,
5+
SkeletonText,
6+
Spacer,
7+
Text,
8+
useTheme,
9+
Screen,
10+
useScreenDimensions,
11+
} from "@artsy/palette-mobile"
12+
import { RouterLink } from "app/system/navigation/RouterLink"
13+
import {
14+
PlaceholderBox,
15+
ProvidePlaceholderContext,
16+
RandomWidthPlaceholderText,
17+
} from "app/utils/placeholders"
18+
import { times } from "lodash"
19+
import { FlatList, GestureResponderEvent, useWindowDimensions } from "react-native"
20+
import { isTablet } from "react-native-device-info"
21+
22+
export const CARD_IMAGE_WIDTH = 295
23+
export const CARD_IMAGE_HEIGHT = 230
24+
25+
interface CardWithMetaDataProps {
26+
isFluid?: boolean
27+
href: string
28+
imageURL: string | null | undefined
29+
title: string
30+
subtitle: string
31+
tag: string
32+
onPress?: (event: GestureResponderEvent) => void
33+
testId?: string
34+
}
35+
36+
export const CardWithMetaData: React.FC<CardWithMetaDataProps> = (props) => {
37+
const { isFluid, href, imageURL, title, subtitle, tag, onPress } = props
38+
const numColumns = useNumColumns()
39+
40+
const { space } = useTheme()
41+
const { width } = useWindowDimensions()
42+
43+
return (
44+
<>
45+
<Flex width={isFluid ? "100%" : CARD_IMAGE_WIDTH}>
46+
<RouterLink onPress={onPress} testID="article-card" to={href}>
47+
<Flex width={isFluid ? "100%" : CARD_IMAGE_WIDTH} overflow="hidden">
48+
{!!imageURL &&
49+
(isFluid ? (
50+
<Image
51+
src={imageURL}
52+
// aspect ratio is fixed to 1.33 to match the old image aspect ratio
53+
aspectRatio={1.33}
54+
width={width / numColumns - 2 * space(2)}
55+
/>
56+
) : (
57+
<Image src={imageURL} width={CARD_IMAGE_WIDTH} height={CARD_IMAGE_HEIGHT} />
58+
))}
59+
<Spacer y={1} />
60+
<Text numberOfLines={2} ellipsizeMode="tail" variant="sm-display" mb={0.5}>
61+
{title}
62+
</Text>
63+
{!!subtitle && (
64+
<Text color="black60" variant="xs">
65+
{subtitle}
66+
</Text>
67+
)}
68+
{!!tag && (
69+
<Text color="black100" variant="xs">
70+
{tag}
71+
</Text>
72+
)}
73+
</Flex>
74+
</RouterLink>
75+
</Flex>
76+
</>
77+
)
78+
}
79+
80+
export const CardWithMetaDataSkeleton: React.FC = () => {
81+
return (
82+
<Flex width={CARD_IMAGE_WIDTH} overflow="hidden">
83+
<SkeletonBox height={CARD_IMAGE_HEIGHT} width={CARD_IMAGE_WIDTH} />
84+
<Spacer y={1} />
85+
<SkeletonText variant="sm-display" mb={0.5}>
86+
Example title
87+
</SkeletonText>
88+
<SkeletonText variant="xs">Example subtitle</SkeletonText>
89+
<SkeletonText variant="xs">Berlin • Oct 8-Nov 9</SkeletonText>
90+
</Flex>
91+
)
92+
}
93+
94+
interface CardsWithMetaDataListPlaceholderProps {
95+
title?: string
96+
testID?: string
97+
}
98+
99+
export const CardsWithMetaDataListPlaceholder: React.FC<CardsWithMetaDataListPlaceholderProps> = ({
100+
title = "Artsy Editorial",
101+
testID,
102+
}) => {
103+
const numColumns = useNumColumns()
104+
105+
return (
106+
<Screen>
107+
<Screen.AnimatedHeader title={title} />
108+
<Screen.Body fullwidth>
109+
<ProvidePlaceholderContext>
110+
<Flex testID={testID} flexDirection="column" justifyContent="space-between" height="100%">
111+
<FlatList
112+
numColumns={numColumns}
113+
key={`${numColumns}`}
114+
ListHeaderComponent={() => <ListHeader title={title} />}
115+
data={times(6)}
116+
keyExtractor={(item) => `${item}-${numColumns}`}
117+
renderItem={({ item }) => {
118+
return (
119+
<CardWithMetaDataListItem index={item} key={item}>
120+
<PlaceholderBox aspectRatio={1.33} width="100%" marginBottom={10} />
121+
<RandomWidthPlaceholderText minWidth={50} maxWidth={100} marginTop={1} />
122+
<RandomWidthPlaceholderText
123+
height={18}
124+
minWidth={200}
125+
maxWidth={200}
126+
marginTop={1}
127+
/>
128+
<RandomWidthPlaceholderText minWidth={100} maxWidth={100} marginTop={1} />
129+
<Spacer y={2} />
130+
</CardWithMetaDataListItem>
131+
)
132+
}}
133+
ItemSeparatorComponent={() => <Spacer y={4} />}
134+
onEndReachedThreshold={1}
135+
/>
136+
</Flex>
137+
</ProvidePlaceholderContext>
138+
</Screen.Body>
139+
</Screen>
140+
)
141+
}
142+
143+
export const ListHeader = ({ title = "" }) => (
144+
<Text mx={2} variant="lg-display" mb={2}>
145+
{title}
146+
</Text>
147+
)
148+
149+
interface CardWithMetaDataListItemProps {
150+
index: number
151+
}
152+
153+
export const CardWithMetaDataListItem: React.FC<CardWithMetaDataListItemProps> = ({
154+
children,
155+
index,
156+
}) => {
157+
const numColumns = useNumColumns()
158+
159+
if (numColumns === 1) {
160+
return <Flex mx={2}>{children}</Flex>
161+
}
162+
163+
const ml = index % numColumns === 0 ? 2 : 1
164+
const mr = index % numColumns < numColumns - 1 ? 1 : 2
165+
166+
return (
167+
<Flex flex={1 / numColumns} flexDirection="row" ml={ml} mr={mr}>
168+
<Flex flex={1}>{children}</Flex>
169+
</Flex>
170+
)
171+
}
172+
173+
export const useNumColumns = () => {
174+
const { orientation } = useScreenDimensions()
175+
176+
if (!isTablet()) {
177+
return 1
178+
}
179+
180+
return orientation === "portrait" ? 2 : 3
181+
}

src/app/Components/ShowCard.tsx

+11-63
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
1-
import {
2-
Flex,
3-
Image,
4-
SkeletonBox,
5-
SkeletonText,
6-
Spacer,
7-
Text,
8-
useTheme,
9-
} from "@artsy/palette-mobile"
101
import { toTitleCase } from "@artsy/to-title-case"
112
import { ShowCard_show$data } from "__generated__/ShowCard_show.graphql"
12-
import { ImageWithFallback } from "app/Components/ImageWithFallback/ImageWithFallback"
13-
import { RouterLink } from "app/system/navigation/RouterLink"
3+
import { CardWithMetaData } from "app/Components/Cards/CardWithMetaData"
144
import { compact } from "lodash"
15-
import { GestureResponderEvent, useWindowDimensions, View, ViewProps } from "react-native"
5+
import { GestureResponderEvent, ViewProps } from "react-native"
166
import { createFragmentContainer, graphql } from "react-relay"
177

18-
const WIDTH = 295
19-
const HEIGHT = 230
20-
218
interface ShowCardProps extends ViewProps {
229
show: ShowCard_show$data
2310
isFluid: boolean
@@ -38,41 +25,16 @@ export const ShowCard: React.FC<ShowCardProps> = ({ show, isFluid, onPress }) =>
3825

3926
const formattedCityAndDate = compact([showCity, formattedDate]).join(" • ")
4027

41-
const { space } = useTheme()
42-
const { width } = useWindowDimensions()
43-
4428
return (
45-
<Flex width={isFluid ? "100%" : WIDTH}>
46-
<RouterLink haptic onPress={onPress} to={show.href}>
47-
<Flex width={isFluid ? "100%" : WIDTH} overflow="hidden">
48-
{!!imageURL &&
49-
(isFluid ? (
50-
<>
51-
<View style={{ width }}>
52-
<Image
53-
src={imageURL}
54-
// aspect ratio is fixed to 1.33 to match the old image aspect ratio
55-
aspectRatio={1.33}
56-
// 40 here comes from the mx={2} from the parent component
57-
width={width - 2 * space(2)}
58-
/>
59-
</View>
60-
</>
61-
) : (
62-
<ImageWithFallback src={imageURL} width={WIDTH} height={HEIGHT} />
63-
))}
64-
65-
<Spacer y={1} />
66-
<Text numberOfLines={2} ellipsizeMode="tail" variant="sm-display" mb={0.5}>
67-
{show.name}
68-
</Text>
69-
<Text variant="xs" color="black60">
70-
{show.partner?.name}
71-
</Text>
72-
<Text variant="xs">{formattedCityAndDate}</Text>
73-
</Flex>
74-
</RouterLink>
75-
</Flex>
29+
<CardWithMetaData
30+
isFluid={isFluid}
31+
href={show.href}
32+
imageURL={imageURL}
33+
title={show.name}
34+
subtitle={show.partner?.name}
35+
tag={formattedCityAndDate}
36+
onPress={onPress}
37+
/>
7638
)
7739
}
7840

@@ -136,17 +98,3 @@ export const ShowCardContainer = createFragmentContainer(ShowCard, {
13698
}
13799
`,
138100
})
139-
140-
export const SkeletonShowCard: React.FC = () => {
141-
return (
142-
<Flex width={WIDTH} overflow="hidden">
143-
<SkeletonBox height={HEIGHT} width={WIDTH} />
144-
<Spacer y={1} />
145-
<SkeletonText variant="sm-display" mb={0.5}>
146-
Example Show
147-
</SkeletonText>
148-
<SkeletonText variant="xs"></SkeletonText>
149-
<SkeletonText variant="xs">Berlin • Oct 8-Nov 9</SkeletonText>
150-
</Flex>
151-
)
152-
}

0 commit comments

Comments
 (0)