@@ -15,7 +15,7 @@ import {
15
15
import { IconName , IconNames } from '@blueprintjs/icons' ;
16
16
import classNames from 'classnames' ;
17
17
import { Location } from 'history' ;
18
- import { useCallback , useMemo , useState } from 'react' ;
18
+ import React , { useMemo , useState } from 'react' ;
19
19
import { Translation } from 'react-i18next' ;
20
20
import { NavLink , Route , Routes , useLocation } from 'react-router-dom' ;
21
21
import { i18nDefaultLangKeys } from 'src/i18n/i18next' ;
@@ -42,10 +42,53 @@ export type NavbarEntryInfo = {
42
42
hiddenInBreakpoints ?: ( 'xs' | 'sm' | 'md' | 'lg' ) [ ] ; // hide text in Blueprint breakpoints
43
43
} ;
44
44
45
- type CreateNavlinkFunction = ( navbarEntry : NavbarEntryInfo ) => React . ReactElement ;
45
+ const MobileHamburger : React . FC < { navlinks : NavbarEntryInfo [ ] } > = ( { navlinks } ) => {
46
+ // Don't render drawer when there are 0 navlinks in it
47
+ const [ mobileSideMenuOpen , setMobileSideMenuOpen ] = useState ( false ) ;
48
+ const shownNavlinks = navlinks . filter ( e => ! e . disabled ) ;
49
+ const renderDrawer = shownNavlinks . length > 0 ;
50
+
51
+ const { courseShortName, courseId } = useSession ( ) ;
52
+
53
+ return (
54
+ < NavbarGroup align = { Alignment . LEFT } >
55
+ { renderDrawer && (
56
+ < Button
57
+ onClick = { ( ) => setMobileSideMenuOpen ( ! mobileSideMenuOpen ) }
58
+ icon = { IconNames . MENU }
59
+ large = { true }
60
+ minimal = { true }
61
+ />
62
+ ) }
63
+ < NavLink
64
+ className = "NavigationBar__link"
65
+ to = { Constants . playgroundOnly ? '/' : courseId == null ? '/welcome' : `/courses/${ courseId } ` }
66
+ >
67
+ < NavbarHeading >
68
+ < Button className = "app-title" minimal icon = { IconNames . SYMBOL_DIAMOND } >
69
+ { courseShortName || Constants . sourceAcademyDeploymentName }
70
+ </ Button >
71
+ </ NavbarHeading >
72
+ </ NavLink >
73
+ { renderDrawer && (
74
+ < Drawer
75
+ isOpen = { mobileSideMenuOpen }
76
+ position = "left"
77
+ onClose = { ( ) => setMobileSideMenuOpen ( false ) }
78
+ title = ""
79
+ className = { Classes . DARK }
80
+ style = { { overflowY : 'auto' } }
81
+ >
82
+ { shownNavlinks . map ( ( entry , i ) => (
83
+ < MobileNavLink key = { i } { ...entry } handleClick = { ( ) => setMobileSideMenuOpen ( false ) } />
84
+ ) ) }
85
+ </ Drawer >
86
+ ) }
87
+ </ NavbarGroup >
88
+ ) ;
89
+ } ;
46
90
47
91
const NavigationBar : React . FC = ( ) => {
48
- const [ mobileSideMenuOpen , setMobileSideMenuOpen ] = useState ( false ) ;
49
92
const { isMobileBreakpoint } = useResponsive ( ) ;
50
93
const location = useLocation ( ) ;
51
94
const {
@@ -66,79 +109,6 @@ const NavigationBar: React.FC = () => {
66
109
67
110
FocusStyleManager . onlyShowFocusOnTabs ( ) ;
68
111
69
- const createMobileNavlink : CreateNavlinkFunction = useCallback (
70
- navbarEntry => (
71
- < NavLink
72
- to = { navbarEntry . to }
73
- className = { ( { isActive } ) =>
74
- classNames ( Classes . BUTTON , Classes . MINIMAL , Classes . LARGE , { [ Classes . ACTIVE ] : isActive } )
75
- }
76
- onClick = { ( ) => setMobileSideMenuOpen ( false ) }
77
- key = { navbarEntry . text }
78
- >
79
- < Icon icon = { navbarEntry . icon } />
80
- < div >
81
- < Translation ns = "commons" keyPrefix = "navigationBar" >
82
- { t =>
83
- t ( navbarEntry . text as keyof i18nDefaultLangKeys [ 'commons' ] [ 'navigationBar' ] , {
84
- defaultValue : navbarEntry . text
85
- } )
86
- }
87
- </ Translation >
88
- </ div >
89
- { navbarEntry . hasNotifications && (
90
- < NotificationBadge
91
- notificationFilter = { filterNotificationsByType ( navbarEntry . text ) }
92
- disableHover = { true }
93
- />
94
- ) }
95
- </ NavLink >
96
- ) ,
97
- [ setMobileSideMenuOpen ]
98
- ) ;
99
-
100
- const wrapWithMobileHamburger = ( navlinks : ( React . ReactElement | null ) [ ] ) => {
101
- // Don't render drawer when there are 0 navlinks in it
102
- const nonNullNavlinks = navlinks . filter ( e => e !== null ) ;
103
- const renderDrawer = nonNullNavlinks . length > 0 ;
104
-
105
- return (
106
- < NavbarGroup align = { Alignment . LEFT } >
107
- { renderDrawer && (
108
- < Button
109
- onClick = { ( ) => setMobileSideMenuOpen ( ! mobileSideMenuOpen ) }
110
- icon = { IconNames . MENU }
111
- large = { true }
112
- minimal = { true }
113
- />
114
- ) }
115
- < NavLink
116
- className = { classNames ( 'NavigationBar__link' , Classes . BUTTON , Classes . MINIMAL ) }
117
- to = {
118
- Constants . playgroundOnly ? '/' : courseId == null ? '/welcome' : `/courses/${ courseId } `
119
- }
120
- >
121
- < Icon icon = { IconNames . SYMBOL_DIAMOND } />
122
- < NavbarHeading style = { { paddingBottom : '0px' } } >
123
- { courseShortName || Constants . sourceAcademyDeploymentName }
124
- </ NavbarHeading >
125
- </ NavLink >
126
- { renderDrawer && (
127
- < Drawer
128
- isOpen = { mobileSideMenuOpen }
129
- position = "left"
130
- onClose = { ( ) => setMobileSideMenuOpen ( false ) }
131
- title = ""
132
- className = { Classes . DARK }
133
- style = { { overflowY : 'auto' } }
134
- >
135
- { navlinks }
136
- </ Drawer >
137
- ) }
138
- </ NavbarGroup >
139
- ) ;
140
- } ;
141
-
142
112
const fullAcademyNavbarLeftAssessmentsInfo : NavbarEntryInfo [ ] = useMemo (
143
113
( ) =>
144
114
assessmentTypesToNavlinkInfo ( {
@@ -211,27 +181,29 @@ const NavigationBar: React.FC = () => {
211
181
212
182
const renderPlaygroundOnlyNavbarLeftDesktop = ( ) => (
213
183
< NavbarGroup align = { Alignment . LEFT } >
214
- { renderNavlinksFromInfo ( playgroundOnlyNavbarLeftInfo , createDesktopNavlink ) }
184
+ { playgroundOnlyNavbarLeftInfo . map ( ( entry , i ) => (
185
+ < DesktopNavLink key = { i } { ...entry } />
186
+ ) ) }
215
187
</ NavbarGroup >
216
188
) ;
217
189
218
- const renderPlaygroundOnlyNavbarLeftMobile = ( ) =>
219
- wrapWithMobileHamburger (
220
- renderNavlinksFromInfo ( playgroundOnlyNavbarLeftInfo , createMobileNavlink )
221
- ) ;
190
+ const renderPlaygroundOnlyNavbarLeftMobile = ( ) => (
191
+ < MobileHamburger navlinks = { playgroundOnlyNavbarLeftInfo } />
192
+ ) ;
222
193
223
194
const renderFullAcademyNavbarLeftDesktop = ( ) => {
195
+ const entries = assessmentTypesToNavlinkInfo ( {
196
+ assessmentTypes,
197
+ courseId,
198
+ isEnrolledInACourse
199
+ } ) ;
200
+
224
201
const desktopNavbarLeftPopoverContent = (
225
202
< Navbar >
226
203
< NavbarGroup >
227
- { renderNavlinksFromInfo (
228
- assessmentTypesToNavlinkInfo ( {
229
- assessmentTypes,
230
- courseId,
231
- isEnrolledInACourse
232
- } ) ,
233
- createDesktopNavlink
234
- ) }
204
+ { entries . map ( ( entry , i ) => (
205
+ < DesktopNavLink key = { i } { ...entry } />
206
+ ) ) }
235
207
</ NavbarGroup >
236
208
</ Navbar >
237
209
) ;
@@ -259,26 +231,31 @@ const NavigationBar: React.FC = () => {
259
231
disabled = { ! enableDesktopPopover }
260
232
>
261
233
< NavLink
262
- className = { classNames ( 'NavigationBar__link' , Classes . BUTTON , Classes . MINIMAL , {
263
- [ Classes . ACTIVE ] : highlightDesktopLogo ( location )
264
- } ) }
234
+ className = "NavigationBar__link"
265
235
to = { courseId == null ? '/welcome' : `/courses/${ courseId } ` }
266
236
>
267
- < Icon icon = { IconNames . SYMBOL_DIAMOND } />
268
- < NavbarHeading style = { { paddingBottom : '0px' } } >
269
- { courseShortName || Constants . sourceAcademyDeploymentName }
237
+ < NavbarHeading >
238
+ < Button
239
+ className = "app-title"
240
+ minimal
241
+ icon = { IconNames . SYMBOL_DIAMOND }
242
+ active = { highlightDesktopLogo ( location ) }
243
+ >
244
+ { courseShortName || Constants . sourceAcademyDeploymentName }
245
+ </ Button >
270
246
</ NavbarHeading >
271
247
</ NavLink >
272
248
</ Popover >
273
- { renderNavlinksFromInfo ( fullAcademyNavbarLeftCommonInfo , createDesktopNavlink ) }
249
+ { fullAcademyNavbarLeftCommonInfo . map ( ( entry , i ) => (
250
+ < DesktopNavLink key = { i } { ...entry } />
251
+ ) ) }
274
252
</ NavbarGroup >
275
253
) ;
276
254
} ;
277
255
278
- const renderFullAcademyNavbarLeftMobile = ( ) =>
279
- wrapWithMobileHamburger (
280
- renderNavlinksFromInfo ( fullAcademyMobileNavbarLeftInfoWithAssessments , createMobileNavlink )
281
- ) ;
256
+ const renderFullAcademyNavbarLeftMobile = ( ) => (
257
+ < MobileHamburger navlinks = { fullAcademyMobileNavbarLeftInfoWithAssessments } />
258
+ ) ;
282
259
283
260
const commonNavbarRight = (
284
261
< NavbarGroup align = { Alignment . RIGHT } >
@@ -364,46 +341,63 @@ const playgroundOnlyNavbarLeftInfo: NavbarEntryInfo[] = [
364
341
// }
365
342
] ;
366
343
367
- export const renderNavlinksFromInfo = (
368
- navbarEntries : NavbarEntryInfo [ ] ,
369
- createNavlink : CreateNavlinkFunction
370
- ) : ( React . ReactElement | null ) [ ] =>
371
- navbarEntries . map ( entry => {
372
- if ( entry . disabled ) {
373
- return null ;
374
- }
375
-
376
- return createNavlink ( entry ) ;
377
- } ) ;
344
+ export const DesktopNavLink : React . FC < NavbarEntryInfo > = props => {
345
+ const responsive = useResponsive ( ) ;
346
+ const shouldHide = props . hiddenInBreakpoints ?. some ( bp => responsive [ bp ] ) ;
347
+ return props . disabled ? null : (
348
+ < NavLink
349
+ className = { ( { isActive } ) => classNames ( isActive && Classes . ACTIVE ) }
350
+ to = { props . to }
351
+ key = { props . text }
352
+ title = { props . text }
353
+ >
354
+ < Button minimal icon = { props . icon } >
355
+ { ! shouldHide && (
356
+ < Translation ns = "commons" keyPrefix = "navigationBar" >
357
+ { t =>
358
+ t ( props . text as keyof i18nDefaultLangKeys [ 'commons' ] [ 'navigationBar' ] , {
359
+ defaultValue : props . text
360
+ } )
361
+ }
362
+ </ Translation >
363
+ ) }
364
+ </ Button >
365
+ { props . hasNotifications && (
366
+ < NotificationBadge
367
+ notificationFilter = { filterNotificationsByType ( props . text ) }
368
+ disableHover = { true }
369
+ />
370
+ ) }
371
+ </ NavLink >
372
+ ) ;
373
+ } ;
378
374
379
- export const createDesktopNavlink : CreateNavlinkFunction = navbarEntry => (
380
- < NavLink
381
- className = { ( { isActive } ) =>
382
- classNames ( Classes . BUTTON , Classes . MINIMAL , {
383
- [ Classes . ACTIVE ] : isActive
384
- } )
385
- }
386
- to = { navbarEntry . to }
387
- key = { navbarEntry . text }
388
- title = { navbarEntry . text }
389
- >
390
- < Icon icon = { navbarEntry . icon } />
391
- < div className = { classNames ( navbarEntry . hiddenInBreakpoints ?. map ( bp => `hidden-${ bp } ` ) ) } >
392
- < Translation ns = "commons" keyPrefix = "navigationBar" >
393
- { t =>
394
- t ( navbarEntry . text as keyof i18nDefaultLangKeys [ 'commons' ] [ 'navigationBar' ] , {
395
- defaultValue : navbarEntry . text
396
- } )
397
- }
398
- </ Translation >
399
- </ div >
400
- { navbarEntry . hasNotifications && (
401
- < NotificationBadge
402
- notificationFilter = { filterNotificationsByType ( navbarEntry . text ) }
403
- disableHover = { true }
404
- />
405
- ) }
406
- </ NavLink >
407
- ) ;
375
+ const MobileNavLink : React . FC <
376
+ NavbarEntryInfo & { handleClick ?: React . MouseEventHandler < HTMLAnchorElement > }
377
+ > = props =>
378
+ props . disabled ? null : (
379
+ < NavLink
380
+ to = { props . to }
381
+ className = { ( { isActive } ) => classNames ( isActive && Classes . ACTIVE ) }
382
+ onClick = { props . handleClick }
383
+ key = { props . text }
384
+ >
385
+ < Button minimal large icon = { props . icon } >
386
+ < Translation ns = "commons" keyPrefix = "navigationBar" >
387
+ { t =>
388
+ t ( props . text as keyof i18nDefaultLangKeys [ 'commons' ] [ 'navigationBar' ] , {
389
+ defaultValue : props . text
390
+ } )
391
+ }
392
+ </ Translation >
393
+ </ Button >
394
+ { props . hasNotifications && (
395
+ < NotificationBadge
396
+ notificationFilter = { filterNotificationsByType ( props . text ) }
397
+ disableHover = { true }
398
+ />
399
+ ) }
400
+ </ NavLink >
401
+ ) ;
408
402
409
403
export default NavigationBar ;
0 commit comments