1
1
import { QueryClient , queryOptions , useSuspenseQuery } from '@tanstack/react-query' ;
2
2
import Markdown from 'react-markdown' ;
3
- import { LoaderFunctionArgs , useLoaderData } from 'react-router-dom' ;
3
+ import { Link , LoaderFunctionArgs , useLoaderData } from 'react-router-dom' ;
4
4
import rehypeKatex from 'rehype-katex' ;
5
5
import remarkGfm from 'remark-gfm' ;
6
6
import remarkMath from 'remark-math' ;
7
7
8
8
import { Badge } from '@/components/ui/badge' ;
9
9
import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui/card' ;
10
10
import { getQuestionDetails } from '@/services/question-service' ;
11
+ import { Separator } from '@/components/ui/separator' ;
12
+ import {
13
+ Breadcrumb ,
14
+ BreadcrumbItem ,
15
+ BreadcrumbLink ,
16
+ BreadcrumbList ,
17
+ BreadcrumbSeparator ,
18
+ } from '@/components/ui/breadcrumb' ;
19
+ import { ScrollArea } from '@/components/ui/scroll-area' ;
20
+ import { Fragment } from 'react/jsx-runtime' ;
21
+ import { cn } from '@/lib/utils' ;
11
22
12
23
const questionDetailsQuery = ( id : number ) =>
13
24
queryOptions ( {
@@ -23,35 +34,103 @@ export const loader =
23
34
return { questionId } ;
24
35
} ;
25
36
37
+ const BreadCrumbLinks = ( { id, title } : { id : number ; title : string } ) => {
38
+ const links = [
39
+ {
40
+ link : '/' ,
41
+ label : 'Home' ,
42
+ } ,
43
+ {
44
+ link : '/questions' ,
45
+ label : 'Questions' ,
46
+ } ,
47
+ {
48
+ link : '/questions/' + id ,
49
+ label : '1. ' + title ,
50
+ isCurrent : true ,
51
+ } ,
52
+ ] ;
53
+ return links . map ( ( { link, label, isCurrent } , index ) => (
54
+ < Fragment key = { index } >
55
+ < BreadcrumbItem >
56
+ < BreadcrumbLink asChild >
57
+ < Link to = { link } className = { cn ( isCurrent && 'text-secondary-foreground' ) } >
58
+ { label }
59
+ </ Link >
60
+ </ BreadcrumbLink >
61
+ </ BreadcrumbItem >
62
+ { index < links . length - 1 && < BreadcrumbSeparator /> }
63
+ </ Fragment >
64
+ ) ) ;
65
+ } ;
66
+
26
67
export const QuestionDetails = ( ) => {
27
68
const { questionId } = useLoaderData ( ) as Awaited < ReturnType < ReturnType < typeof loader > > > ;
28
69
const { data : details } = useSuspenseQuery ( questionDetailsQuery ( questionId ) ) ;
29
70
return (
30
- < Card >
31
- < CardHeader >
32
- < div className = 'flex flex-col gap-4' >
33
- < div className = 'flex w-full items-center gap-4' >
34
- < CardTitle className = 'text-2xl' > { details . title } </ CardTitle >
35
- < Badge className = 'flex w-min grow-0' > { details . difficulty } </ Badge >
36
- </ div >
37
- < div className = 'flex flex-wrap items-center gap-1' >
38
- { details . topics . map ( ( v , i ) => (
39
- < Badge className = 'flex w-min grow-0 whitespace-nowrap' key = { i } >
40
- { v }
41
- </ Badge >
42
- ) ) }
43
- </ div >
44
- </ div >
45
- </ CardHeader >
46
- < CardContent >
47
- < Markdown
48
- rehypePlugins = { [ rehypeKatex ] }
49
- remarkPlugins = { [ remarkMath , remarkGfm ] }
50
- className = 'prose prose-neutral'
51
- >
52
- { details . description }
53
- </ Markdown >
54
- </ CardContent >
55
- </ Card >
71
+ < div className = 'flex h-[calc(100dvh-64px)] w-full flex-col' >
72
+ < div className = 'bg-secondary/50 flex w-full p-4 px-6' >
73
+ < Breadcrumb >
74
+ < BreadcrumbList >
75
+ < BreadCrumbLinks id = { details . id } title = { details . title } />
76
+ </ BreadcrumbList >
77
+ </ Breadcrumb >
78
+ </ div >
79
+ < div className = 'flex flex-1 overflow-hidden' >
80
+ < Card className = 'border-border m-4 w-1/3 max-w-[500px] overflow-hidden p-4 md:w-2/5' >
81
+ < ScrollArea className = 'h-full' >
82
+ < CardHeader >
83
+ < div className = 'flex flex-col gap-4' >
84
+ < div className = 'flex w-full items-center gap-4' >
85
+ < CardTitle className = 'text-2xl' >
86
+ { details . id } . { details . title }
87
+ </ CardTitle >
88
+ </ div >
89
+ < div className = 'flex flex-wrap items-center gap-1' >
90
+ < Badge variant = 'secondary' className = 'flex w-min grow-0' >
91
+ { details . difficulty }
92
+ </ Badge >
93
+ < Separator orientation = 'vertical' className = 'mx-2 h-4' />
94
+ < span className = 'text-sm font-medium' > Topics:</ span >
95
+ { details . topics . map ( ( v , i ) => (
96
+ < Badge
97
+ variant = 'secondary'
98
+ className = 'flex w-min grow-0 whitespace-nowrap'
99
+ key = { i }
100
+ >
101
+ { v }
102
+ </ Badge >
103
+ ) ) }
104
+ </ div >
105
+ </ div >
106
+ </ CardHeader >
107
+ < CardContent >
108
+ < Markdown
109
+ rehypePlugins = { [ rehypeKatex ] }
110
+ remarkPlugins = { [ remarkMath , remarkGfm ] }
111
+ className = 'prose prose-neutral text-card-foreground prose-strong:text-card-foreground leading-normal'
112
+ components = { {
113
+ code : ( { children, className, ...rest } ) => {
114
+ // const isCodeBlock = /language-(\w+)/.exec(className || '');
115
+
116
+ return (
117
+ < code
118
+ { ...rest }
119
+ className = 'bg-secondary text-secondary-foreground rounded px-1.5 py-1 font-mono'
120
+ >
121
+ { children }
122
+ </ code >
123
+ ) ;
124
+ } ,
125
+ } }
126
+ >
127
+ { details . description }
128
+ </ Markdown >
129
+ </ CardContent >
130
+ </ ScrollArea >
131
+ </ Card >
132
+ < div className = 'flex flex-1 flex-col' />
133
+ </ div >
134
+ </ div >
56
135
) ;
57
136
} ;
0 commit comments