Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve accessibility for navigation on web (#6120)

* improve accessibility for bottom bar tabs

* improve a11y for left nav

* group main content into <main>

* use flex_1 rather than absoluteFill

authored by

Samuel Newman and committed by
GitHub
b0c5a37d 97721163

+169 -69
+2 -2
src/view/shell/bottom-bar/BottomBar.tsx
··· 200 200 accessibilityLabel={_(msg`Chat`)} 201 201 accessibilityHint={ 202 202 numUnreadMessages.count > 0 203 - ? `${numUnreadMessages.numUnread} unread` 203 + ? _(msg`${numUnreadMessages.numUnread} unread items`) 204 204 : '' 205 205 } 206 206 /> ··· 227 227 accessibilityHint={ 228 228 numUnreadNotifications === '' 229 229 ? '' 230 - : `${numUnreadNotifications} unread` 230 + : _(msg`${numUnreadNotifications} unread items`) 231 231 } 232 232 /> 233 233 <Btn
+61 -45
src/view/shell/bottom-bar/BottomBarWeb.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 import Animated from 'react-native-reanimated' 4 - import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 4 import {msg, Trans} from '@lingui/macro' 6 5 import {useLingui} from '@lingui/react' 7 6 import {useNavigationState} from '@react-navigation/native' 8 7 9 8 import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransform' 10 - import {usePalette} from '#/lib/hooks/usePalette' 11 - import {clamp} from '#/lib/numbers' 12 9 import {getCurrentRoute, isTab} from '#/lib/routes/helpers' 13 10 import {makeProfileLink} from '#/lib/routes/links' 14 11 import {CommonNavigatorParams} from '#/lib/routes/types' 15 - import {s} from '#/lib/styles' 16 12 import {useUnreadMessageCount} from '#/state/queries/messages/list-converations' 17 13 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 18 14 import {useSession} from '#/state/session' ··· 23 19 import {Text} from '#/view/com/util/text/Text' 24 20 import {Logo} from '#/view/icons/Logo' 25 21 import {Logotype} from '#/view/icons/Logotype' 22 + import {atoms as a, useTheme} from '#/alf' 26 23 import { 27 24 Bell_Filled_Corner0_Rounded as BellFilled, 28 25 Bell_Stroke2_Corner0_Rounded as Bell, ··· 46 43 export function BottomBarWeb() { 47 44 const {_} = useLingui() 48 45 const {hasSession, currentAccount} = useSession() 49 - const pal = usePalette('default') 50 - const safeAreaInsets = useSafeAreaInsets() 46 + const t = useTheme() 51 47 const footerMinimalShellTransform = useMinimalShellFooterTransform() 52 48 const {requestSwitchToAccount} = useLoggedOutViewControls() 53 49 const closeAllActiveElements = useCloseAllActiveElements() ··· 69 65 70 66 return ( 71 67 <Animated.View 68 + role="navigation" 72 69 style={[ 73 70 styles.bottomBar, 74 71 styles.bottomBarWeb, 75 - pal.view, 76 - pal.border, 77 - {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)}, 72 + t.atoms.bg, 73 + t.atoms.border_contrast_low, 78 74 footerMinimalShellTransform, 79 75 ]}> 80 76 {hasSession ? ( ··· 84 80 const Icon = isActive ? HomeFilled : Home 85 81 return ( 86 82 <Icon 83 + aria-hidden={true} 87 84 width={iconWidth + 1} 88 - style={[styles.ctrlIcon, pal.text, styles.homeIcon]} 85 + style={[styles.ctrlIcon, t.atoms.text, styles.homeIcon]} 89 86 /> 90 87 ) 91 88 }} ··· 95 92 const Icon = isActive ? MagnifyingGlassFilled : MagnifyingGlass 96 93 return ( 97 94 <Icon 95 + aria-hidden={true} 98 96 width={iconWidth + 2} 99 - style={[styles.ctrlIcon, pal.text, styles.searchIcon]} 97 + style={[styles.ctrlIcon, t.atoms.text, styles.searchIcon]} 100 98 /> 101 99 ) 102 100 }} ··· 104 102 105 103 {hasSession && ( 106 104 <> 107 - <NavItem routeName="Messages" href="/messages"> 105 + <NavItem 106 + routeName="Messages" 107 + href="/messages" 108 + badge={ 109 + unreadMessageCount.count > 0 110 + ? unreadMessageCount.numUnread 111 + : undefined 112 + }> 108 113 {({isActive}) => { 109 114 const Icon = isActive ? MessageFilled : Message 110 115 return ( 111 - <> 112 - <Icon 113 - width={iconWidth - 1} 114 - style={[styles.ctrlIcon, pal.text, styles.messagesIcon]} 115 - /> 116 - {unreadMessageCount.count > 0 && ( 117 - <View style={styles.notificationCount}> 118 - <Text style={styles.notificationCountLabel}> 119 - {unreadMessageCount.numUnread} 120 - </Text> 121 - </View> 122 - )} 123 - </> 116 + <Icon 117 + aria-hidden={true} 118 + width={iconWidth - 1} 119 + style={[ 120 + styles.ctrlIcon, 121 + t.atoms.text, 122 + styles.messagesIcon, 123 + ]} 124 + /> 124 125 ) 125 126 }} 126 127 </NavItem> 127 - <NavItem routeName="Notifications" href="/notifications"> 128 + <NavItem 129 + routeName="Notifications" 130 + href="/notifications" 131 + badge={notificationCountStr}> 128 132 {({isActive}) => { 129 133 const Icon = isActive ? BellFilled : Bell 130 134 return ( 131 - <> 132 - <Icon 133 - width={iconWidth} 134 - style={[styles.ctrlIcon, pal.text, styles.bellIcon]} 135 - /> 136 - {notificationCountStr !== '' && ( 137 - <View style={styles.notificationCount}> 138 - <Text style={styles.notificationCountLabel}> 139 - {notificationCountStr} 140 - </Text> 141 - </View> 142 - )} 143 - </> 135 + <Icon 136 + aria-hidden={true} 137 + width={iconWidth} 138 + style={[styles.ctrlIcon, t.atoms.text, styles.bellIcon]} 139 + /> 144 140 ) 145 141 }} 146 142 </NavItem> ··· 158 154 const Icon = isActive ? UserCircleFilled : UserCircle 159 155 return ( 160 156 <Icon 157 + aria-hidden={true} 161 158 width={iconWidth} 162 - style={[styles.ctrlIcon, pal.text, styles.profileIcon]} 159 + style={[ 160 + styles.ctrlIcon, 161 + t.atoms.text, 162 + styles.profileIcon, 163 + ]} 163 164 /> 164 165 ) 165 166 }} ··· 184 185 <View style={{flexDirection: 'row', alignItems: 'center', gap: 12}}> 185 186 <Logo width={32} /> 186 187 <View style={{paddingTop: 4}}> 187 - <Logotype width={80} fill={pal.text.color} /> 188 + <Logotype width={80} fill={t.atoms.text.color} /> 188 189 </View> 189 190 </View> 190 191 ··· 193 194 onPress={showCreateAccount} 194 195 accessibilityHint={_(msg`Sign up`)} 195 196 accessibilityLabel={_(msg`Sign up`)}> 196 - <Text type="md" style={[{color: 'white'}, s.bold]}> 197 + <Text type="md" style={[{color: 'white'}, a.font_bold]}> 197 198 <Trans>Sign up</Trans> 198 199 </Text> 199 200 </Button> ··· 203 204 onPress={showSignIn} 204 205 accessibilityHint={_(msg`Sign in`)} 205 206 accessibilityLabel={_(msg`Sign in`)}> 206 - <Text type="md" style={[pal.text, s.bold]}> 207 + <Text type="md" style={[t.atoms.text, a.font_bold]}> 207 208 <Trans>Sign in</Trans> 208 209 </Text> 209 210 </Button> ··· 219 220 children: (props: {isActive: boolean}) => React.ReactChild 220 221 href: string 221 222 routeName: string 222 - }> = ({children, href, routeName}) => { 223 + badge?: string 224 + }> = ({children, href, routeName, badge}) => { 225 + const {_} = useLingui() 223 226 const {currentAccount} = useSession() 224 227 const currentRoute = useNavigationState(state => { 225 228 if (!state) { ··· 235 238 : isTab(currentRoute.name, routeName) 236 239 237 240 return ( 238 - <Link href={href} style={styles.ctrl} navigationAction="navigate"> 241 + <Link 242 + href={href} 243 + style={[styles.ctrl, a.pb_lg]} 244 + navigationAction="navigate" 245 + aria-role="link" 246 + aria-label={routeName} 247 + accessible={true}> 239 248 {children({isActive})} 249 + {!!badge && ( 250 + <View 251 + style={styles.notificationCount} 252 + aria-label={_(msg`${badge} unread items`)}> 253 + <Text style={styles.notificationCountLabel}>{badge}</Text> 254 + </View> 255 + )} 240 256 </Link> 241 257 ) 242 258 }
+9 -6
src/view/shell/createNativeStackNavigatorWithAuth.tsx
··· 34 34 import {Deactivated} from '#/screens/Deactivated' 35 35 import {Onboarding} from '#/screens/Onboarding' 36 36 import {SignupQueued} from '#/screens/SignupQueued' 37 + import {atoms as a} from '#/alf' 37 38 import {BottomBarWeb} from './bottom-bar/BottomBarWeb' 38 39 import {DesktopLeftNav} from './desktop/LeftNav' 39 40 import {DesktopRightNav} from './desktop/RightNav' ··· 137 138 138 139 return ( 139 140 <NavigationContent> 140 - <NativeStackView 141 - {...rest} 142 - state={state} 143 - navigation={navigation} 144 - descriptors={newDescriptors} 145 - /> 141 + <View role="main" style={a.flex_1}> 142 + <NativeStackView 143 + {...rest} 144 + state={state} 145 + navigation={navigation} 146 + descriptors={newDescriptors} 147 + /> 148 + </View> 146 149 {isWeb && showBottomBar && <BottomBarWeb />} 147 150 {isWeb && !showBottomBar && ( 148 151 <>
+97 -16
src/view/shell/desktop/LeftNav.tsx
··· 151 151 } 152 152 function NavItem({count, href, icon, iconFilled, label}: NavItemProps) { 153 153 const t = useTheme() 154 + const {_} = useLingui() 154 155 const {currentAccount} = useSession() 155 156 const {gtMobile, gtTablet} = useBreakpoints() 156 157 const isTablet = gtMobile && !gtTablet ··· 199 200 // @ts-ignore web only -prf 200 201 href={href} 201 202 dataSet={{noUnderline: 1}} 202 - accessibilityRole="tab" 203 + role="link" 203 204 accessibilityLabel={label} 204 205 accessibilityHint=""> 205 206 <View ··· 219 220 {isCurrent ? iconFilled : icon} 220 221 {typeof count === 'string' && count ? ( 221 222 <Text 223 + accessibilityLabel={_(msg`${count} unread items`)} 224 + accessibilityHint="" 225 + accessible={true} 222 226 style={[ 223 227 a.absolute, 224 228 a.text_xs, ··· 307 311 <View style={[a.flex_row, a.pl_md, a.pt_xl]}> 308 312 <Button 309 313 disabled={isFetchingHandle} 310 - label={_(msg`New post`)} 314 + label={_(msg`Compose new post`)} 311 315 onPress={onPressCompose} 312 316 size="large" 313 317 variant="solid" ··· 331 335 <NavItem 332 336 href="/messages" 333 337 count={numUnreadMessages.numUnread} 334 - icon={<Message style={pal.text} width={NAV_ICON_WIDTH} />} 335 - iconFilled={<MessageFilled style={pal.text} width={NAV_ICON_WIDTH} />} 338 + icon={ 339 + <Message style={pal.text} aria-hidden={true} width={NAV_ICON_WIDTH} /> 340 + } 341 + iconFilled={ 342 + <MessageFilled 343 + style={pal.text} 344 + aria-hidden={true} 345 + width={NAV_ICON_WIDTH} 346 + /> 347 + } 336 348 label={_(msg`Chat`)} 337 349 /> 338 350 ) ··· 351 363 352 364 return ( 353 365 <View 366 + role="navigation" 354 367 style={[ 355 368 styles.leftNav, 356 369 isTablet && styles.leftNavTablet, ··· 371 384 372 385 <NavItem 373 386 href="/" 374 - icon={<Home width={NAV_ICON_WIDTH} style={pal.text} />} 375 - iconFilled={<HomeFilled width={NAV_ICON_WIDTH} style={pal.text} />} 387 + icon={ 388 + <Home 389 + aria-hidden={true} 390 + width={NAV_ICON_WIDTH} 391 + style={pal.text} 392 + /> 393 + } 394 + iconFilled={ 395 + <HomeFilled 396 + aria-hidden={true} 397 + width={NAV_ICON_WIDTH} 398 + style={pal.text} 399 + /> 400 + } 376 401 label={_(msg`Home`)} 377 402 /> 378 403 <NavItem 379 404 href="/search" 380 - icon={<MagnifyingGlass style={pal.text} width={NAV_ICON_WIDTH} />} 405 + icon={ 406 + <MagnifyingGlass 407 + style={pal.text} 408 + aria-hidden={true} 409 + width={NAV_ICON_WIDTH} 410 + /> 411 + } 381 412 iconFilled={ 382 - <MagnifyingGlassFilled style={pal.text} width={NAV_ICON_WIDTH} /> 413 + <MagnifyingGlassFilled 414 + style={pal.text} 415 + aria-hidden={true} 416 + width={NAV_ICON_WIDTH} 417 + /> 383 418 } 384 419 label={_(msg`Search`)} 385 420 /> 386 421 <NavItem 387 422 href="/notifications" 388 423 count={numUnreadNotifications} 389 - icon={<Bell width={NAV_ICON_WIDTH} style={pal.text} />} 390 - iconFilled={<BellFilled width={NAV_ICON_WIDTH} style={pal.text} />} 424 + icon={ 425 + <Bell 426 + aria-hidden={true} 427 + width={NAV_ICON_WIDTH} 428 + style={pal.text} 429 + /> 430 + } 431 + iconFilled={ 432 + <BellFilled 433 + aria-hidden={true} 434 + width={NAV_ICON_WIDTH} 435 + style={pal.text} 436 + /> 437 + } 391 438 label={_(msg`Notifications`)} 392 439 /> 393 440 <ChatNavItem /> ··· 396 443 icon={ 397 444 <Hashtag 398 445 style={pal.text as FontAwesomeIconStyle} 446 + aria-hidden={true} 399 447 width={NAV_ICON_WIDTH} 400 448 /> 401 449 } 402 450 iconFilled={ 403 451 <HashtagFilled 404 452 style={pal.text as FontAwesomeIconStyle} 453 + aria-hidden={true} 405 454 width={NAV_ICON_WIDTH} 406 455 /> 407 456 } ··· 409 458 /> 410 459 <NavItem 411 460 href="/lists" 412 - icon={<List style={pal.text} width={NAV_ICON_WIDTH} />} 413 - iconFilled={<ListFilled style={pal.text} width={NAV_ICON_WIDTH} />} 461 + icon={ 462 + <List 463 + style={pal.text} 464 + aria-hidden={true} 465 + width={NAV_ICON_WIDTH} 466 + /> 467 + } 468 + iconFilled={ 469 + <ListFilled 470 + style={pal.text} 471 + aria-hidden={true} 472 + width={NAV_ICON_WIDTH} 473 + /> 474 + } 414 475 label={_(msg`Lists`)} 415 476 /> 416 477 <NavItem 417 478 href={currentAccount ? makeProfileLink(currentAccount) : '/'} 418 - icon={<UserCircle width={NAV_ICON_WIDTH} style={pal.text} />} 479 + icon={ 480 + <UserCircle 481 + aria-hidden={true} 482 + width={NAV_ICON_WIDTH} 483 + style={pal.text} 484 + /> 485 + } 419 486 iconFilled={ 420 - <UserCircleFilled width={NAV_ICON_WIDTH} style={pal.text} /> 487 + <UserCircleFilled 488 + aria-hidden={true} 489 + width={NAV_ICON_WIDTH} 490 + style={pal.text} 491 + /> 421 492 } 422 493 label={_(msg`Profile`)} 423 494 /> 424 495 <NavItem 425 496 href="/settings" 426 - icon={<Settings width={NAV_ICON_WIDTH} style={pal.text} />} 497 + icon={ 498 + <Settings 499 + aria-hidden={true} 500 + width={NAV_ICON_WIDTH} 501 + style={pal.text} 502 + /> 503 + } 427 504 iconFilled={ 428 - <SettingsFilled width={NAV_ICON_WIDTH} style={pal.text} /> 505 + <SettingsFilled 506 + aria-hidden={true} 507 + width={NAV_ICON_WIDTH} 508 + style={pal.text} 509 + /> 429 510 } 430 511 label={_(msg`Settings`)} 431 512 />