Skip to content

Commit fd94309

Browse files
yeion7SaraVieira
authored andcommitted
navigation a11y (codesandbox#2611)
* improve search a11y * add reakit in user menu * add focus buttons * links * add menu label * fix typecheck * get focus on first item
1 parent 8ad2cbe commit fd94309

File tree

9 files changed

+148
-116
lines changed

9 files changed

+148
-116
lines changed

packages/app/src/app/components/HeaderSearchBar/HeaderSearchBar.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@ export const HeaderSearchBar = () => {
1616
};
1717

1818
return (
19-
<Container>
20-
<form onSubmit={handleFormSubmit}>
19+
<form onSubmit={handleFormSubmit}>
20+
<Container role="search">
2121
<Input
22+
aria-label="Search sandboxes"
2223
placeholder="Search sandboxes"
2324
value={query}
2425
onChange={handleChange}
26+
id="search-sandboxes"
27+
type="text"
2528
/>
26-
<SearchButton>
29+
<SearchButton type="submit" aria-labelledby="search-sandboxes">
2730
<SearchIcon />
2831
</SearchButton>
29-
</form>
30-
</Container>
32+
</Container>
33+
</form>
3134
);
3235
};

packages/app/src/app/overmind/actions.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,6 @@ export const signInCliClicked: AsyncAction = async ({ state, actions }) => {
107107
}
108108
};
109109

110-
export const userMenuOpened: Action = ({ state }) => {
111-
state.userMenuOpen = true;
112-
};
113-
114-
export const userMenuClosed: Action = ({ state }) => {
115-
state.userMenuOpen = false;
116-
};
117-
118110
export const addNotification: Action<{
119111
message: string;
120112
type: NotificationType;

packages/app/src/app/overmind/state.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ type State = {
1616
hasLoadedApp: boolean;
1717
jwt: string;
1818
isAuthenticating: boolean;
19-
userMenuOpen: boolean;
2019
authToken: string;
2120
error: string;
2221
contributors: string[];
@@ -63,7 +62,6 @@ export const state: State = {
6362
connected: true,
6463
notifications: [],
6564
contributors: [],
66-
userMenuOpen: false,
6765
isLoadingZeit: false,
6866
isLoadingCLI: false,
6967
isLoadingGithub: false,

packages/app/src/app/pages/common/Navigation/elements.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const Action = styled.div<{ noHover?: boolean }>`
3838
cursor: pointer;
3939
color: white;
4040
opacity: 0.8;
41+
background: transparent;
42+
border: none;
4143
4244
${props =>
4345
props.noHover

packages/app/src/app/pages/common/Navigation/index.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const Navigation = inject('store', 'signals')(
5353
<a href="/?from-app=1">
5454
<LogoWithBorder height={35} width={35} />
5555
</a>
56-
<Border />
56+
<Border role="presentation" />
5757
<Title>{title}</Title>
5858
</TitleWrapper>
5959
<Wrapper>
@@ -63,7 +63,11 @@ export const Navigation = inject('store', 'signals')(
6363
{matches =>
6464
matches || searchNoInput ? (
6565
<Tooltip placement="bottom" content="Search All Sandboxes">
66-
<Link style={{ color: 'white' }} to={searchUrl()}>
66+
<Link
67+
style={{ color: 'white' }}
68+
to={searchUrl()}
69+
aria-label="Search All Sandboxes"
70+
>
6771
<SearchIcon height={35} />
6872
</Link>
6973
</Tooltip>
@@ -105,8 +109,14 @@ export const Navigation = inject('store', 'signals')(
105109
<Observer>
106110
{({ store }) => (
107111
<Action
112+
as="button"
108113
style={{ position: 'relative', fontSize: '1.25rem' }}
109114
onClick={open}
115+
aria-label={
116+
store.userNotifications.unreadCount > 0
117+
? 'Show Notifications'
118+
: 'No Notifications'
119+
}
110120
>
111121
<Tooltip
112122
placement="bottom"
@@ -129,12 +139,14 @@ export const Navigation = inject('store', 'signals')(
129139
)}
130140

131141
<Action
142+
as="button"
132143
style={{ fontSize: '1.125rem' }}
133144
onClick={() =>
134145
modalOpened({
135146
modal: 'newSandbox',
136147
})
137148
}
149+
aria-label="New Sandbox"
138150
>
139151
<Tooltip placement="bottom" content="New Sandbox">
140152
<PlusIcon height={35} />

packages/app/src/app/pages/common/UserMenu/Menu/elements.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import styled from 'styled-components';
2+
import { Link } from 'react-router-dom';
23
import delayEffect from '@codesandbox/common/lib/utils/animation/delay-effect';
34

45
export const Container = styled.div`
5-
position: absolute;
66
background-color: ${props => props.theme.background4};
77
box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.75);
88
99
${delayEffect(0)};
10-
top: 35px;
11-
12-
right: 0;
1310
1411
min-width: 200px;
1512
1613
z-index: 20;
1714
`;
1815

16+
export const LinkItem = styled(Link)`
17+
text-decoration: none;
18+
color: inherit;
19+
`;
20+
1921
export const Item = styled.div<{ to?: string; href?: string }>`
2022
transition: 0.3s ease all;
2123
display: flex;

packages/app/src/app/pages/common/UserMenu/Menu/index.tsx

Lines changed: 95 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import {
1515
} from '@codesandbox/common/lib/utils/url-generator';
1616
import PatronBadge from '@codesandbox/common/lib/utils/badges/PatronBadge';
1717
import track from '@codesandbox/common/lib/utils/analytics';
18+
import { MenuItem, Menu as ReakitMenu, MenuStateReturn } from 'reakit/Menu';
1819
// @ts-ignore
1920
import InfoIcon from '-!svg-react-loader!@codesandbox/common/lib/icons/sandbox.svg';
2021

21-
import { Container, Item, Icon, Separator } from './elements';
22+
import { Container, Item, Icon, Separator, LinkItem } from './elements';
2223
import { FeedbackIcon } from './FeedbackIcon';
2324

2425
interface Props {
@@ -28,6 +29,7 @@ interface Props {
2829
openStorageManagement: () => void;
2930
openFeedback: () => void;
3031
signOut: () => void;
32+
menuProps: MenuStateReturn;
3133
}
3234

3335
export const Menu = ({
@@ -37,87 +39,103 @@ export const Menu = ({
3739
openStorageManagement,
3840
openFeedback,
3941
signOut,
42+
menuProps,
4043
}: Props) => {
4144
useEffect(() => {
42-
track('User Menu Open');
43-
}, []);
45+
if (menuProps.visible) {
46+
track('User Menu Open');
47+
}
48+
}, [menuProps.visible]);
4449

4550
return (
46-
<Container>
47-
<Item as={Link} to={profileUrl(username)}>
48-
<Icon>
49-
<UserIcon />
50-
</Icon>
51-
My Profile
52-
</Item>
53-
54-
<Separator />
55-
56-
<Item as={Link} to={dashboardUrl()}>
57-
<Icon>
58-
<InfoIcon />
59-
</Icon>
60-
Dashboard
61-
</Item>
62-
63-
<Item as="a" href="/docs">
64-
<Icon>
65-
<BookIcon />
66-
</Icon>
67-
Documentation
68-
</Item>
69-
70-
{curator && (
71-
<Item as={Link} to={curatorUrl()}>
51+
<ReakitMenu {...menuProps} aria-label="user options">
52+
<Container>
53+
<LinkItem to={profileUrl(username)} tabIndex={-1}>
54+
<MenuItem as={Item} {...menuProps}>
55+
<Icon>
56+
<UserIcon />
57+
</Icon>
58+
My Profile
59+
</MenuItem>
60+
</LinkItem>
61+
62+
<Separator role="presentation" />
63+
<LinkItem to={dashboardUrl()}>
64+
<MenuItem as={Item} {...menuProps}>
65+
<Icon>
66+
<InfoIcon />
67+
</Icon>
68+
Dashboard
69+
</MenuItem>
70+
</LinkItem>
71+
72+
<LinkItem as="a" href="/docs">
73+
<MenuItem as={Item} {...menuProps}>
74+
<Icon>
75+
<BookIcon />
76+
</Icon>
77+
Documentation
78+
</MenuItem>
79+
</LinkItem>
80+
81+
{curator && (
82+
<LinkItem to={curatorUrl()}>
83+
<MenuItem as={Item} {...menuProps}>
84+
<Icon>
85+
<span style={{ width: 14 }} role="img" aria-label="Star">
86+
87+
</span>
88+
</Icon>
89+
Curator Dashboard
90+
</MenuItem>
91+
</LinkItem>
92+
)}
93+
94+
<LinkItem as={Link} to={patronUrl()}>
95+
<MenuItem as={Item} {...menuProps}>
96+
<Icon>
97+
<PatronBadge
98+
style={{ width: 24, margin: '-6px -5px' }}
99+
size={24}
100+
/>
101+
</Icon>
102+
Patron Page
103+
</MenuItem>
104+
</LinkItem>
105+
106+
<Separator role="presentation" />
107+
108+
<MenuItem as={Item} {...menuProps} onClick={openStorageManagement}>
72109
<Icon>
73-
<span style={{ width: 14 }} role="img" aria-label="Star">
74-
75-
</span>
110+
<FolderIcon />
76111
</Icon>
77-
Curator Dashboard
78-
</Item>
79-
)}
80-
81-
<Item as={Link} to={patronUrl()}>
82-
<Icon>
83-
<PatronBadge style={{ width: 24, margin: '-6px -5px' }} size={24} />
84-
</Icon>
85-
Patron Page
86-
</Item>
87-
88-
<Separator />
89-
90-
<Item onClick={openStorageManagement}>
91-
<Icon>
92-
<FolderIcon />
93-
</Icon>
94-
Storage Management
95-
</Item>
96-
97-
<Item onClick={openPreferences}>
98-
<Icon>
99-
<SettingsIcon />
100-
</Icon>
101-
Preferences
102-
</Item>
103-
104-
<Separator />
105-
106-
<Item onClick={openFeedback}>
107-
<Icon>
108-
<FeedbackIcon />
109-
</Icon>
110-
Submit Feedback
111-
</Item>
112-
113-
<Separator />
114-
115-
<Item onClick={signOut}>
116-
<Icon>
117-
<ExitIcon />
118-
</Icon>
119-
Sign out
120-
</Item>
121-
</Container>
112+
Storage Management
113+
</MenuItem>
114+
115+
<MenuItem as={Item} {...menuProps} onClick={openPreferences}>
116+
<Icon>
117+
<SettingsIcon />
118+
</Icon>
119+
Preferences
120+
</MenuItem>
121+
122+
<Separator role="presentation" />
123+
124+
<MenuItem as={Item} {...menuProps} onClick={openFeedback}>
125+
<Icon>
126+
<FeedbackIcon />
127+
</Icon>
128+
Submit Feedback
129+
</MenuItem>
130+
131+
<Separator role="presentation" />
132+
<MenuItem as={Item} {...menuProps} onClick={signOut}>
133+
<Icon>
134+
<ExitIcon />
135+
</Icon>
136+
Sign out
137+
</MenuItem>
138+
</Container>
139+
</ReakitMenu>
122140
);
123141
};

packages/app/src/app/pages/common/UserMenu/elements.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import Row from '@codesandbox/common/lib/components/flex/Row';
33

44
export const ClickableContainer = styled(Row)`
55
cursor: pointer;
6+
background: transparent;
7+
border: none;
8+
appearance: none !important;
69
`;
710

811
export const ProfileImage = styled.img`

0 commit comments

Comments
 (0)