1
- // TypeScript React
2
1
import React from 'react' ;
3
2
import cn from 'classnames' ;
4
3
import styles from './case-studies-card.module.css' ;
4
+ import { AndroidIcon , AppleIcon , ServerIcon , ComputerIcon , GlobusIcon } from '@rescui/icons' ;
5
+ import { CaseStudyItem , CaseStudyType , isExternalCaseStudy , Platform } from '../case-studies' ;
5
6
6
- type Platform =
7
- | 'android'
8
- | 'ios'
9
- | 'desktop'
10
- | 'frontend'
11
- | 'backend'
12
- | 'compose-multi-platform' ;
13
-
14
- type CaseType = 'multiplatform' | 'server-side' ;
15
-
16
- type Media =
17
- | { type : 'youtube' ; url : string }
18
- | { type : 'image' ; path : string } ;
19
-
20
- interface Signature {
21
- // markdown allowed (e.g., **Name Surname**, Role)
22
- line1 : string ;
23
- // plain text
24
- line2 : string ;
25
- }
26
-
27
- export interface CaseCardItem {
28
- logos ?: [ string ] | [ string , string ] ; // 0–2 logos, local paths or filenames
29
- description : string ; // markdown-enabled text
30
- signature ?: Signature ;
31
- readMoreUrl ?: string ; // "Read the full story →"
32
- exploreUrl ?: string ; // "Explore the stories"
33
- type : CaseType ;
34
- platforms ?: Platform [ ] ; // platform icons row
35
- media ?: Media ; // youtube or local image
36
- /** Optional: mark case as selected for the home page */
37
- featuredOnHome ?: boolean ;
38
- }
39
7
40
8
/**
41
9
* Resolve asset path from YAML:
@@ -44,8 +12,6 @@ export interface CaseCardItem {
44
12
*/
45
13
const resolveAssetPath = ( v ?: string ) => {
46
14
if ( ! v ) return '' ;
47
- const lower = v . toLowerCase ( ) ;
48
- if ( lower . startsWith ( 'http://' ) || lower . startsWith ( 'https://' ) || v . startsWith ( '/' ) ) return v ;
49
15
return `/images/case-studies/${ v } ` ;
50
16
} ;
51
17
@@ -72,27 +38,46 @@ const mdToHtml = (md: string) => {
72
38
return withLinks ;
73
39
} ;
74
40
75
- const badgeText : Record < CaseType , string > = {
41
+ const badgeText : Record < CaseStudyType , string > = {
76
42
'multiplatform' : 'Kotlin Multiplatform' ,
77
- 'server-side' : 'Server-side' ,
43
+ 'server-side' : 'Server-side'
78
44
} ;
79
45
80
- const badgeClass : Record < CaseType , string > = {
46
+ const badgeClass : Record < CaseStudyType , string > = {
81
47
'multiplatform' : 'badgeMultiplatform' ,
82
- 'server-side' : 'badgeServerSide' ,
48
+ 'server-side' : 'badgeServerSide'
83
49
} ;
84
50
85
51
// Platform icon path builder. If you keep icons in (for example) /images/platforms/*.svg,
86
52
// they’ll be resolved automatically by key. If an icon is missing, we still render the label.
87
- const platformIconPath = ( p : Platform ) => `/images/platforms/${ p } .svg` ;
53
+ const getPlatformIcon = ( p : Platform ) => {
54
+ switch ( p ) {
55
+ case 'android' :
56
+ return < AndroidIcon /> ;
57
+ case 'ios' :
58
+ return < AppleIcon /> ;
59
+ case 'desktop' :
60
+ return < ComputerIcon /> ;
61
+ case 'frontend' :
62
+ return < GlobusIcon /> ;
63
+ case 'backend' :
64
+ return < ServerIcon /> ;
65
+ case 'compose-multiplatform' :
66
+ return < img className = { styles . platformIcon } src = { '/images/platforms/compose-multiplatform.svg' }
67
+ alt = "Compose Multiplatform icon"
68
+ onError = { ( e ) => hideBrokenIcon ( e . currentTarget ) } /> ;
69
+ default :
70
+ return null ;
71
+ }
72
+ } ;
88
73
89
74
type Props = {
90
- item : CaseCardItem ;
75
+ item : CaseStudyItem ;
91
76
className ?: string ;
92
77
} ;
93
78
94
79
export const CaseStudyCard : React . FC < Props > = ( { item, className } ) => {
95
- const logos = item . logos ?? [ ] ;
80
+ const logos = item . logo ?? [ ] ;
96
81
const logoSrc1 = resolveAssetPath ( logos [ 0 ] ) ;
97
82
const logoSrc2 = resolveAssetPath ( logos [ 1 ] ) ;
98
83
@@ -142,13 +127,11 @@ export const CaseStudyCard: React.FC<Props> = ({ item, className }) => {
142
127
</ div >
143
128
) }
144
129
145
- { /* Description (markdown) */ }
146
130
< div
147
131
className = { styles . description }
148
132
dangerouslySetInnerHTML = { { __html : mdToHtml ( item . description ) } }
149
133
/>
150
134
151
- { /* Signature (optional) */ }
152
135
{ item . signature && (
153
136
< div className = { styles . signature } >
154
137
< div
@@ -163,43 +146,33 @@ export const CaseStudyCard: React.FC<Props> = ({ item, className }) => {
163
146
{ item . platforms && item . platforms . length > 0 && (
164
147
< div className = { styles . platforms } aria-label = "Platforms" >
165
148
{ item . platforms . map ( ( p ) => {
166
- const iconSrc = platformIconPath ( p ) ;
149
+ const iconSrc = getPlatformIcon ( p ) ;
167
150
// We render icon + label; if icon path 404s, the label remains visible
168
151
return (
169
152
< span key = { p } className = { styles . platform } >
170
- < img className = { styles . platformIcon } src = { iconSrc } alt = { `${ p } icon` } onError = { ( e ) => hideBrokenIcon ( e . currentTarget ) } />
153
+ < >
154
+ { getPlatformIcon ( p ) }
155
+ </ >
171
156
< span className = { styles . platformLabel } > { humanizePlatform ( p ) } </ span >
172
157
</ span >
173
158
) ;
174
159
} ) }
175
160
</ div >
176
161
) }
177
-
178
- { /* Actions */ }
179
- { ( item . readMoreUrl || item . exploreUrl ) && (
162
+ { ( isExternalCaseStudy ( item ) ) ? (
180
163
< div className = { styles . actions } >
181
- { item . readMoreUrl && (
182
- < a
183
- className = { styles . link }
184
- href = { item . readMoreUrl }
185
- target = "_blank"
186
- rel = "noopener noreferrer"
187
- >
188
- Read the full story →
189
- </ a >
190
- ) }
191
- { item . exploreUrl && (
164
+ { item . externalLink && (
192
165
< a
193
166
className = { styles . button }
194
- href = { item . exploreUrl }
167
+ href = { item . externalLink }
195
168
target = "_blank"
196
169
rel = "noopener noreferrer"
197
170
>
198
- Explore the stories
171
+ { item . externalLinkText || 'Read the full story' }
199
172
</ a >
200
173
) }
201
174
</ div >
202
- ) }
175
+ ) : null }
203
176
</ article >
204
177
) ;
205
178
} ;
@@ -247,7 +220,7 @@ function hideBrokenIcon(img: HTMLImageElement) {
247
220
*/
248
221
function humanizePlatform ( p : Platform ) : string {
249
222
switch ( p ) {
250
- case 'compose-multi-platform ' :
223
+ case 'compose-multiplatform ' :
251
224
return 'Compose Multiplatform' ;
252
225
case 'frontend' :
253
226
return 'Frontend' ;
0 commit comments