Skip to content

Commit 23d4450

Browse files
committed
feat: more flexible custom link rendering
1 parent 12e7d44 commit 23d4450

File tree

5 files changed

+287
-266
lines changed

5 files changed

+287
-266
lines changed

src/components/OperationCard.tsx

Lines changed: 181 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { languageAtom, useTranslation } from '../i18n/i18n'
1616
import { createCustomLevel, findLevelByStageName } from '../models/level'
1717
import { getLocalizedOperatorName } from '../models/operator'
1818
import { Paragraphs } from './Paragraphs'
19-
import { ReLinkDiv } from './ReLinkDiv'
19+
import { ReLinkRenderer } from './ReLink'
2020
import { UserName } from './UserName'
2121
import { EDifficulty } from './entity/EDifficulty'
2222
import { EDifficultyLevel, NeoELevel } from './entity/ELevel'
@@ -36,101 +36,105 @@ export const NeoOperationCard = ({
3636
const { data: levels } = useLevels()
3737

3838
return (
39-
<Card interactive={true} elevation={Elevation.TWO} className="relative">
40-
<ReLinkDiv
39+
<li className="relative">
40+
<ReLinkRenderer
4141
search={{ op: operation.id }}
42-
className="h-full flex flex-col gap-2"
43-
>
44-
<div className="flex">
45-
<Tooltip2
46-
content={operation.parsedContent.doc.title}
47-
className="grow flex-1 whitespace-nowrap overflow-hidden text-ellipsis"
42+
render={({ onClick, onKeyDown }) => (
43+
<Card
44+
interactive
45+
className="h-full flex flex-col gap-2"
46+
elevation={Elevation.TWO}
47+
tabIndex={0}
48+
onClick={onClick}
49+
onKeyDown={onKeyDown}
4850
>
49-
<H4 className="p-0 m-0 mr-20 flex items-center overflow-hidden">
50-
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
51-
{operation.parsedContent.doc.title}
52-
</span>
53-
{operation.status === CopilotInfoStatusEnum.Private && (
54-
<Tag minimal className="ml-2 shrink-0 font-normal opacity-75">
55-
{t.components.OperationCard.private}
56-
</Tag>
57-
)}
58-
</H4>
59-
</Tooltip2>
60-
</div>
61-
<div className="flex items-center text-slate-900">
62-
<div className="flex flex-wrap">
63-
<NeoELevel
64-
level={
65-
findLevelByStageName(
66-
levels,
67-
operation.parsedContent.stageName,
68-
) || createCustomLevel(operation.parsedContent.stageName)
69-
}
70-
/>
71-
<EDifficulty
72-
difficulty={
73-
operation.parsedContent.difficulty ?? OpDifficulty.UNKNOWN
74-
}
75-
/>
76-
</div>
77-
</div>
78-
<div className="flex-1 flex flex-col gap-2 justify-center">
79-
<div className="text-gray-700 leading-normal">
80-
<Paragraphs
81-
content={operation.parsedContent.doc.details}
82-
limitHeight={21 * 13.5} // 13 lines, 21px per line; the extra 0.5 line is intentional so the `mask` effect is obvious
83-
/>
84-
</div>
85-
<div>
86-
<div className="text-sm text-zinc-600 dark:text-slate-100 mb-2 font-bold">
51+
<Tooltip2
52+
content={operation.parsedContent.doc.title}
53+
className="whitespace-nowrap overflow-hidden text-ellipsis"
54+
>
55+
<H4 className="p-0 m-0 mr-20 flex items-center overflow-hidden">
56+
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
57+
{operation.parsedContent.doc.title}
58+
</span>
59+
{operation.status === CopilotInfoStatusEnum.Private && (
60+
<Tag minimal className="ml-2 shrink-0 font-normal opacity-75">
61+
{t.components.OperationCard.private}
62+
</Tag>
63+
)}
64+
</H4>
65+
</Tooltip2>
66+
67+
<div className="flex items-center text-slate-900">
68+
<NeoELevel
69+
level={
70+
findLevelByStageName(
71+
levels,
72+
operation.parsedContent.stageName,
73+
) || createCustomLevel(operation.parsedContent.stageName)
74+
}
75+
/>
76+
<EDifficulty
77+
difficulty={
78+
operation.parsedContent.difficulty ?? OpDifficulty.UNKNOWN
79+
}
80+
/>
81+
</div>
82+
83+
<div className="grow text-gray-700 leading-normal">
84+
<Paragraphs
85+
content={operation.parsedContent.doc.details}
86+
limitHeight={21 * 13.5} // 13 lines, 21px per line; the extra 0.5 line is intentional so the `mask` effect is obvious
87+
/>
88+
</div>
89+
90+
<div className="text-sm text-zinc-600 dark:text-slate-100 font-bold">
8791
{t.components.OperationCard.operators_and_groups}
8892
</div>
8993
<OperatorTags operation={operation} />
90-
</div>
91-
</div>
9294

93-
<div className="flex">
94-
<div className="flex items-center gap-1.5">
95-
<Icon icon="star" />
96-
<OperationRating
97-
className="text-sm"
98-
operation={operation}
99-
layout="horizontal"
100-
/>
101-
</div>
102-
<div className="flex-1" />
95+
<div className="flex">
96+
<div className="flex items-center gap-1.5">
97+
<Icon icon="star" />
98+
<OperationRating
99+
className="text-sm"
100+
operation={operation}
101+
layout="horizontal"
102+
/>
103+
</div>
104+
<div className="flex-1" />
103105

104-
<Tooltip2
105-
placement="top"
106-
content={t.components.OperationCard.views_count({
107-
count: operation.views,
108-
})}
109-
>
110-
<div>
111-
<Icon icon="eye-open" className="mr-1.5" />
112-
<span>{operation.views}</span>
106+
<Tooltip2
107+
placement="top"
108+
content={t.components.OperationCard.views_count({
109+
count: operation.views,
110+
})}
111+
>
112+
<div>
113+
<Icon icon="eye-open" className="mr-1.5" />
114+
<span>{operation.views}</span>
115+
</div>
116+
</Tooltip2>
113117
</div>
114-
</Tooltip2>
115-
</div>
116118

117-
<div className="flex">
118-
<div>
119-
<Icon icon="time" className="mr-1.5" />
120-
<RelativeTime
121-
Tooltip2Props={{ placement: 'top' }}
122-
moment={operation.uploadTime}
123-
/>
124-
</div>
125-
<div className="flex-1" />
126-
<div className="text-zinc-500">
127-
<Icon icon="user" className="mr-1.5" />
128-
<UserName userId={operation.uploaderId}>
129-
{operation.uploader}
130-
</UserName>
131-
</div>
132-
</div>
133-
</ReLinkDiv>
119+
<div className="flex">
120+
<div>
121+
<Icon icon="time" className="mr-1.5" />
122+
<RelativeTime
123+
Tooltip2Props={{ placement: 'top' }}
124+
moment={operation.uploadTime}
125+
/>
126+
</div>
127+
<div className="flex-1" />
128+
<div className="text-zinc-500">
129+
<Icon icon="user" className="mr-1.5" />
130+
<UserName userId={operation.uploaderId}>
131+
{operation.uploader}
132+
</UserName>
133+
</div>
134+
</div>
135+
</Card>
136+
)}
137+
/>
134138

135139
<CardActions
136140
className="absolute top-4 right-4"
@@ -139,7 +143,7 @@ export const NeoOperationCard = ({
139143
selected={selected}
140144
onSelect={onSelect}
141145
/>
142-
</Card>
146+
</li>
143147
)
144148
}
145149

@@ -148,100 +152,106 @@ export const OperationCard = ({ operation }: { operation: Operation }) => {
148152
const { data: levels } = useLevels()
149153

150154
return (
151-
<Card
152-
interactive={true}
153-
elevation={Elevation.TWO}
154-
className="relative mb-4 sm:mb-2 last:mb-0"
155-
>
156-
<ReLinkDiv search={{ op: operation.id }}>
157-
<div className="flex flex-wrap mb-4 sm:mb-2">
158-
{/* title */}
159-
<div className="flex flex-col gap-3">
160-
<div className="flex gap-2">
161-
<H4 className="inline-block pb-1 border-b-2 border-zinc-200 border-solid mb-2">
162-
{operation.parsedContent.doc.title}
163-
{operation.status === CopilotInfoStatusEnum.Private && (
164-
<Tag minimal className="ml-2 font-normal opacity-75">
165-
{t.components.OperationCard.private}
166-
</Tag>
167-
)}
168-
</H4>
169-
</div>
170-
<H5 className="flex items-center text-slate-900 -mt-3">
171-
<EDifficultyLevel
172-
level={
173-
findLevelByStageName(
174-
levels,
175-
operation.parsedContent.stageName,
176-
) || createCustomLevel(operation.parsedContent.stageName)
177-
}
178-
difficulty={operation.parsedContent.difficulty}
179-
/>
180-
</H5>
181-
</div>
155+
<li className="mb-4 sm:mb-2 last:mb-0">
156+
<ReLinkRenderer
157+
search={{ op: operation.id }}
158+
render={({ onClick, onKeyDown }) => (
159+
<Card
160+
interactive
161+
elevation={Elevation.TWO}
162+
tabIndex={0}
163+
onClick={onClick}
164+
onKeyDown={onKeyDown}
165+
>
166+
<div className="flex flex-wrap mb-4 sm:mb-2">
167+
{/* title */}
168+
<div className="flex flex-col gap-3">
169+
<div className="flex gap-2">
170+
<H4 className="inline-block pb-1 border-b-2 border-zinc-200 border-solid mb-2">
171+
{operation.parsedContent.doc.title}
172+
{operation.status === CopilotInfoStatusEnum.Private && (
173+
<Tag minimal className="ml-2 font-normal opacity-75">
174+
{t.components.OperationCard.private}
175+
</Tag>
176+
)}
177+
</H4>
178+
</div>
179+
<H5 className="flex items-center text-slate-900 -mt-3">
180+
<EDifficultyLevel
181+
level={
182+
findLevelByStageName(
183+
levels,
184+
operation.parsedContent.stageName,
185+
) || createCustomLevel(operation.parsedContent.stageName)
186+
}
187+
difficulty={operation.parsedContent.difficulty}
188+
/>
189+
</H5>
190+
</div>
182191

183-
<div className="grow basis-full xl:basis-0" />
192+
<div className="grow basis-full xl:basis-0" />
184193

185-
{/* meta */}
186-
<div className="flex flex-wrap items-start gap-x-4 gap-y-1 text-zinc-500">
187-
<div className="flex items-center gap-1.5">
188-
<Icon icon="star" />
189-
<OperationRating
190-
className="text-sm"
191-
operation={operation}
192-
layout="horizontal"
193-
/>
194-
</div>
194+
{/* meta */}
195+
<div className="flex flex-wrap items-start gap-x-4 gap-y-1 text-zinc-500">
196+
<div className="flex items-center gap-1.5">
197+
<Icon icon="star" />
198+
<OperationRating
199+
className="text-sm"
200+
operation={operation}
201+
layout="horizontal"
202+
/>
203+
</div>
195204

196-
<Tooltip2
197-
placement="top"
198-
content={t.components.OperationCard.views_count({
199-
count: operation.views,
200-
})}
201-
>
202-
<div>
203-
<Icon icon="eye-open" className="mr-1.5" />
204-
<span>{operation.views}</span>
205-
</div>
206-
</Tooltip2>
205+
<Tooltip2
206+
placement="top"
207+
content={t.components.OperationCard.views_count({
208+
count: operation.views,
209+
})}
210+
>
211+
<div>
212+
<Icon icon="eye-open" className="mr-1.5" />
213+
<span>{operation.views}</span>
214+
</div>
215+
</Tooltip2>
207216

208-
<div>
209-
<Icon icon="time" className="mr-1.5" />
210-
<RelativeTime
211-
Tooltip2Props={{ placement: 'top' }}
212-
moment={operation.uploadTime}
213-
/>
214-
</div>
217+
<div>
218+
<Icon icon="time" className="mr-1.5" />
219+
<RelativeTime
220+
Tooltip2Props={{ placement: 'top' }}
221+
moment={operation.uploadTime}
222+
/>
223+
</div>
215224

216-
<div>
217-
<Icon icon="user" className="mr-1.5" />
218-
<UserName userId={operation.uploaderId}>
219-
{operation.uploader}
220-
</UserName>
225+
<div>
226+
<Icon icon="user" className="mr-1.5" />
227+
<UserName userId={operation.uploaderId}>
228+
{operation.uploader}
229+
</UserName>
230+
</div>
231+
</div>
221232
</div>
222-
</div>
223-
</div>
224-
<div className="flex md:flex-row flex-col gap-4">
225-
<div className="text-gray-700 leading-normal md:w-1/2">
226-
<Paragraphs
227-
content={operation.parsedContent.doc.details}
228-
limitHeight={21 * 13.5} // 13 lines, 21px per line; the extra 0.5 line is intentional so the `mask` effect is obvious
229-
/>
230-
</div>
231-
<div className="md:w-1/2">
232-
<div className="text-sm text-zinc-600 dark:text-slate-100 mb-2 font-bold">
233-
{t.components.OperationCard.operators_and_groups}
233+
<div className="flex md:flex-row flex-col gap-4">
234+
<div className="text-gray-700 leading-normal md:w-1/2">
235+
<Paragraphs
236+
content={operation.parsedContent.doc.details}
237+
limitHeight={21 * 13.5} // 13 lines, 21px per line; the extra 0.5 line is intentional so the `mask` effect is obvious
238+
/>
239+
</div>
240+
<div className="md:w-1/2">
241+
<div className="text-sm text-zinc-600 dark:text-slate-100 mb-2 font-bold">
242+
{t.components.OperationCard.operators_and_groups}
243+
</div>
244+
<OperatorTags operation={operation} />
245+
</div>
234246
</div>
235-
<OperatorTags operation={operation} />
236-
</div>
237-
</div>
238-
</ReLinkDiv>
239-
247+
</Card>
248+
)}
249+
/>
240250
<CardActions
241251
className="absolute top-4 xl:top-12 right-[18px]"
242252
operation={operation}
243253
/>
244-
</Card>
254+
</li>
245255
)
246256
}
247257

0 commit comments

Comments
 (0)