@@ -21,8 +21,15 @@ interface DrillConfig {
2121 questionCount : number ;
2222 difficulty : Difficulty | 'all' ;
2323 selectedQuestionIds ?: string [ ] ;
24+ includeSiblingLanguage ?: boolean ;
2425}
2526
27+ // Define sibling language pairs
28+ const SIBLING_LANGUAGES : Partial < Record < LanguageId , LanguageId > > = {
29+ javascript : 'typescript' ,
30+ typescript : 'javascript' ,
31+ } ;
32+
2633interface DrillState {
2734 currentIndex : number ;
2835 answers : AnswerRecord [ ] ;
@@ -182,15 +189,36 @@ function formatTime(ms: number): string {
182189 return `${ minutes } :${ remainingSeconds . toString ( ) . padStart ( 2 , '0' ) } ` ;
183190}
184191
185- function getCategories ( language : LanguageId ) : string [ ] {
192+ function getCategories ( language : LanguageId , includeSibling = false ) : string [ ] {
186193 const problems = PROBLEMS_BY_LANGUAGE [ language ] || [ ] ;
187194 const categories = new Set ( problems . map ( ( p ) => p . category ) ) ;
188- return Array . from ( categories ) ;
195+
196+ // Include sibling language categories if requested
197+ if ( includeSibling ) {
198+ const siblingLang = SIBLING_LANGUAGES [ language ] ;
199+ if ( siblingLang ) {
200+ const siblingProblems = PROBLEMS_BY_LANGUAGE [ siblingLang ] || [ ] ;
201+ for ( const p of siblingProblems ) {
202+ categories . add ( p . category ) ;
203+ }
204+ }
205+ }
206+
207+ return Array . from ( categories ) . sort ( ) ;
189208}
190209
191210function selectProblems ( language : LanguageId , config : DrillConfig ) : Problem [ ] {
192211 let problems = PROBLEMS_BY_LANGUAGE [ language ] || [ ] ;
193212
213+ // Include sibling language problems if requested
214+ if ( config . includeSiblingLanguage ) {
215+ const siblingLang = SIBLING_LANGUAGES [ language ] ;
216+ if ( siblingLang ) {
217+ const siblingProblems = PROBLEMS_BY_LANGUAGE [ siblingLang ] || [ ] ;
218+ problems = [ ...problems , ...siblingProblems ] ;
219+ }
220+ }
221+
194222 // If specific questions are selected, use only those
195223 if ( config . selectedQuestionIds && config . selectedQuestionIds . length > 0 ) {
196224 const selectedSet = new Set ( config . selectedQuestionIds ) ;
@@ -302,7 +330,11 @@ interface SetupPhaseProps {
302330}
303331
304332function SetupPhase ( { language, onStart } : SetupPhaseProps ) {
305- const categories = getCategories ( language ) ;
333+ const siblingLanguage = SIBLING_LANGUAGES [ language ] ;
334+ const [ includeSibling , setIncludeSibling ] = useState ( false ) ;
335+
336+ // Get categories based on whether sibling is included
337+ const categories = getCategories ( language , includeSibling ) ;
306338 const [ selectedCategories , setSelectedCategories ] = useState < string [ ] > ( [ ] ) ;
307339 const [ questionCount , setQuestionCount ] = useState ( 10 ) ;
308340 const [ difficulty , setDifficulty ] = useState < Difficulty | 'all' > ( 'all' ) ;
@@ -323,13 +355,20 @@ function SetupPhase({ language, onStart }: SetupPhaseProps) {
323355 difficulty,
324356 selectedQuestionIds :
325357 selectedQuestionIds . size > 0 ? Array . from ( selectedQuestionIds ) : undefined ,
358+ includeSiblingLanguage : includeSibling ,
326359 } ) ;
327360 } ;
328361
329362 // Get all problems for browsing (without the random selection limit)
330363 const allFilteredProblems = ( ( ) => {
331364 let problems = PROBLEMS_BY_LANGUAGE [ language ] || [ ] ;
332365
366+ // Include sibling language problems if toggled
367+ if ( includeSibling && siblingLanguage ) {
368+ const siblingProblems = PROBLEMS_BY_LANGUAGE [ siblingLanguage ] || [ ] ;
369+ problems = [ ...problems , ...siblingProblems ] ;
370+ }
371+
333372 // Filter by categories
334373 if ( selectedCategories . length > 0 ) {
335374 problems = problems . filter ( ( p ) => selectedCategories . includes ( p . category ) ) ;
@@ -397,6 +436,34 @@ function SetupPhase({ language, onStart }: SetupPhaseProps) {
397436 </ div >
398437
399438 < div className = "bg-zinc-900 rounded-xl p-6 shadow-sm border border-zinc-800 space-y-6" >
439+ { /* Sibling Language Toggle (only for JS/TS) */ }
440+ { siblingLanguage && (
441+ < div className = "flex items-center justify-between p-4 bg-zinc-800/50 rounded-lg border border-zinc-700" >
442+ < div >
443+ < span className = "block text-sm font-medium text-zinc-300" >
444+ Include { siblingLanguage . charAt ( 0 ) . toUpperCase ( ) + siblingLanguage . slice ( 1 ) } { ' ' }
445+ Questions
446+ </ span >
447+ < span className = "text-xs text-zinc-500" >
448+ Practice both languages together since they share similar syntax
449+ </ span >
450+ </ div >
451+ < button
452+ type = "button"
453+ onClick = { ( ) => setIncludeSibling ( ! includeSibling ) }
454+ className = { `relative w-14 h-8 rounded-full transition-colors duration-200 cursor-pointer ${
455+ includeSibling ? 'bg-blue-500' : 'bg-zinc-600'
456+ } `}
457+ >
458+ < div
459+ className = { `absolute top-1 w-6 h-6 bg-white rounded-full transition-transform duration-200 ${
460+ includeSibling ? 'translate-x-7' : 'translate-x-1'
461+ } `}
462+ />
463+ </ button >
464+ </ div >
465+ ) }
466+
400467 { /* Categories */ }
401468 < div >
402469 < span className = "block text-sm font-medium text-zinc-300 mb-3" >
0 commit comments