Skip to content

Commit 7f9a95d

Browse files
committed
Add stories for mobile menu
1 parent 7956d56 commit 7f9a95d

File tree

8 files changed

+225
-52
lines changed

8 files changed

+225
-52
lines changed

site/.storybook/preview.jsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ export const parameters = {
6464
},
6565
type: "tablet",
6666
},
67+
iphone12: {
68+
name: "iPhone 12",
69+
styles: {
70+
height: "844px",
71+
width: "390px",
72+
},
73+
type: "mobile",
74+
},
6775
terminal: {
6876
name: "Terminal",
6977
styles: {

site/src/components/Button/Button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export const buttonVariants = cva(
2626
"border-none bg-transparent text-content-secondary hover:text-content-primary",
2727
warning:
2828
"border border-border-error text-content-primary bg-surface-error hover:bg-transparent",
29-
ghost: "bg-transparent border-0 hover:bg-surface-secondary",
29+
ghost:
30+
"text-content-primary bg-transparent border-0 hover:bg-surface-secondary",
3031
},
3132

3233
size: {

site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { type Interpolation, type Theme, css, useTheme } from "@emotion/react";
2-
import Button from "@mui/material/Button";
32
import MenuItem from "@mui/material/MenuItem";
4-
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
53
import { DropdownMenuButton } from "components/DropdownMenu/DropdownMenu";
64
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
75
import {
@@ -17,15 +15,13 @@ import { NavLink } from "react-router-dom";
1715
interface DeploymentDropdownProps {
1816
canViewDeployment: boolean;
1917
canViewOrganizations: boolean;
20-
canViewAllUsers: boolean;
2118
canViewAuditLog: boolean;
2219
canViewHealth: boolean;
2320
}
2421

2522
export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
2623
canViewDeployment,
2724
canViewOrganizations,
28-
canViewAllUsers,
2925
canViewAuditLog,
3026
canViewHealth,
3127
}) => {
@@ -35,7 +31,6 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
3531
!canViewAuditLog &&
3632
!canViewOrganizations &&
3733
!canViewDeployment &&
38-
!canViewAllUsers &&
3934
!canViewHealth
4035
) {
4136
return null;
@@ -60,7 +55,6 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
6055
<DeploymentDropdownContent
6156
canViewDeployment={canViewDeployment}
6257
canViewOrganizations={canViewOrganizations}
63-
canViewAllUsers={canViewAllUsers}
6458
canViewAuditLog={canViewAuditLog}
6559
canViewHealth={canViewHealth}
6660
/>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { chromaticWithTablet } from "testHelpers/chromatic";
3+
import { MobileMenu } from "./MobileMenu";
4+
import {
5+
MockPrimaryWorkspaceProxy,
6+
MockProxyLatencies,
7+
MockSupportLinks,
8+
MockUser,
9+
MockUser2,
10+
MockWorkspaceProxies,
11+
} from "testHelpers/entities";
12+
import { fn, userEvent, within } from "@storybook/test";
13+
import { PointerEventsCheckLevel } from "@testing-library/user-event";
14+
import type { FC } from "react";
15+
16+
const meta: Meta<typeof MobileMenu> = {
17+
title: "modules/dashboard/MobileMenu",
18+
parameters: {
19+
layout: "fullscreen",
20+
viewport: {
21+
defaultViewport: "iphone12",
22+
},
23+
},
24+
component: MobileMenu,
25+
args: {
26+
proxyContextValue: {
27+
proxy: {
28+
preferredPathAppURL: "",
29+
preferredWildcardHostname: "",
30+
proxy: MockPrimaryWorkspaceProxy,
31+
},
32+
isLoading: false,
33+
isFetched: true,
34+
setProxy: fn(),
35+
clearProxy: fn(),
36+
refetchProxyLatencies: fn(),
37+
proxyLatencies: MockProxyLatencies,
38+
proxies: MockWorkspaceProxies,
39+
},
40+
user: MockUser,
41+
supportLinks: MockSupportLinks,
42+
docsHref: "https://coder.com/docs",
43+
onSignOut: fn(),
44+
isDefaultOpen: true,
45+
canViewAuditLog: true,
46+
canViewDeployment: true,
47+
canViewHealth: true,
48+
canViewOrganizations: true,
49+
},
50+
decorators: [withNavbarMock],
51+
};
52+
53+
export default meta;
54+
type Story = StoryObj<typeof MobileMenu>;
55+
56+
export const Closed: Story = {
57+
args: {
58+
isDefaultOpen: false,
59+
},
60+
};
61+
62+
export const Admin: Story = {
63+
play: openAdminSettings,
64+
};
65+
66+
export const Auditor: Story = {
67+
args: {
68+
user: MockUser2,
69+
canViewAuditLog: true,
70+
canViewDeployment: false,
71+
canViewHealth: false,
72+
canViewOrganizations: false,
73+
},
74+
play: openAdminSettings,
75+
};
76+
77+
export const OrgAdmin: Story = {
78+
args: {
79+
user: MockUser2,
80+
canViewAuditLog: true,
81+
canViewDeployment: false,
82+
canViewHealth: false,
83+
canViewOrganizations: true,
84+
},
85+
play: openAdminSettings,
86+
};
87+
88+
export const Member: Story = {
89+
args: {
90+
user: MockUser2,
91+
canViewAuditLog: false,
92+
canViewDeployment: false,
93+
canViewHealth: false,
94+
canViewOrganizations: false,
95+
},
96+
};
97+
98+
export const ProxySettings: Story = {
99+
play: async ({ canvasElement }) => {
100+
const user = setupUser();
101+
const body = within(canvasElement.ownerDocument.body);
102+
const menuItem = await body.findByRole("menuitem", {
103+
name: /workspace proxy settings/i,
104+
});
105+
await user.click(menuItem);
106+
},
107+
};
108+
109+
export const UserSettings: Story = {
110+
play: async ({ canvasElement }) => {
111+
const user = setupUser();
112+
const body = within(canvasElement.ownerDocument.body);
113+
const menuItem = await body.findByRole("menuitem", {
114+
name: /user settings/i,
115+
});
116+
await user.click(menuItem);
117+
},
118+
};
119+
120+
function withNavbarMock(Story: FC) {
121+
return (
122+
<div className="h-[72px] border-0 border-b border-solid px-6 flex items-center justify-end">
123+
<Story />
124+
</div>
125+
);
126+
}
127+
128+
function setupUser() {
129+
// It seems the dropdown component is disabling pointer events, which is
130+
// causing Testing Library to throw an error. As a workaround, we can
131+
// disable the pointer events check.
132+
return userEvent.setup({
133+
pointerEventsCheck: PointerEventsCheckLevel.Never,
134+
});
135+
}
136+
137+
async function openAdminSettings({
138+
canvasElement,
139+
}: { canvasElement: HTMLElement }) {
140+
const user = setupUser();
141+
const body = within(canvasElement.ownerDocument.body);
142+
const menuItem = await body.findByRole("menuitem", {
143+
name: /admin settings/i,
144+
});
145+
await user.click(menuItem);
146+
}

site/src/modules/dashboard/Navbar/MobileMenu.tsx

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,33 @@ const itemStyles = {
3434
open: "text-content-primary",
3535
};
3636

37-
type MobileMenuProps = {
37+
type MobileMenuProps = MobileMenuPermissions & {
3838
proxyContextValue?: ProxyContextValue;
3939
user?: TypesGen.User;
4040
supportLinks?: readonly TypesGen.LinkConfig[];
4141
docsHref: string;
4242
onSignOut: () => void;
43+
isDefaultOpen?: boolean; // Useful for storybook
44+
};
45+
46+
type MobileMenuPermissions = {
47+
canViewDeployment: boolean;
48+
canViewOrganizations: boolean;
49+
canViewAuditLog: boolean;
50+
canViewHealth: boolean;
4351
};
4452

4553
export const MobileMenu: FC<MobileMenuProps> = ({
54+
isDefaultOpen,
4655
proxyContextValue,
4756
user,
4857
supportLinks,
4958
docsHref,
5059
onSignOut,
60+
...permissions
5161
}) => {
52-
const [open, setOpen] = useState(false);
62+
const [open, setOpen] = useState(isDefaultOpen);
63+
const hasSomePermission = Object.values(permissions).some((p) => p);
5364

5465
return (
5566
<DropdownMenu open={open} onOpenChange={setOpen}>
@@ -75,8 +86,13 @@ export const MobileMenu: FC<MobileMenuProps> = ({
7586
sideOffset={17}
7687
>
7788
<ProxySettingsSub proxyContextValue={proxyContextValue} />
78-
<DropdownMenuSeparator />
79-
<AdminSettingsSub />
89+
90+
{hasSomePermission && (
91+
<>
92+
<DropdownMenuSeparator />
93+
<AdminSettingsSub {...permissions} />
94+
</>
95+
)}
8096
<DropdownMenuSeparator />
8197
<DropdownMenuItem asChild className={itemStyles.default}>
8298
<a href={docsHref} target="_blank" rel="noreferrer norefereer">
@@ -186,7 +202,12 @@ const ProxySettingsSub: FC<ProxySettingsSubProps> = ({ proxyContextValue }) => {
186202
);
187203
};
188204

189-
const AdminSettingsSub: FC = () => {
205+
const AdminSettingsSub: FC<MobileMenuPermissions> = ({
206+
canViewDeployment,
207+
canViewOrganizations,
208+
canViewAuditLog,
209+
canViewHealth,
210+
}) => {
190211
const [open, setOpen] = useState(false);
191212

192213
return (
@@ -206,37 +227,45 @@ const AdminSettingsSub: FC = () => {
206227
</DropdownMenuItem>
207228
</CollapsibleTrigger>
208229
<CollapsibleContent>
209-
<DropdownMenuItem
210-
asChild
211-
className={cn(itemStyles.default, itemStyles.sub)}
212-
>
213-
<Link to="/deployment/general">Deployment</Link>
214-
</DropdownMenuItem>
215-
<DropdownMenuItem
216-
asChild
217-
className={cn(itemStyles.default, itemStyles.sub)}
218-
>
219-
<Link to="/organizations">
220-
Organizations
221-
<FeatureStageBadge
222-
contentType="beta"
223-
size="sm"
224-
showTooltip={false}
225-
/>
226-
</Link>
227-
</DropdownMenuItem>
228-
<DropdownMenuItem
229-
asChild
230-
className={cn(itemStyles.default, itemStyles.sub)}
231-
>
232-
<Link to="/audit">Audit logs</Link>
233-
</DropdownMenuItem>
234-
<DropdownMenuItem
235-
asChild
236-
className={cn(itemStyles.default, itemStyles.sub)}
237-
>
238-
<Link to="/health">Healthcheck</Link>
239-
</DropdownMenuItem>
230+
{canViewDeployment && (
231+
<DropdownMenuItem
232+
asChild
233+
className={cn(itemStyles.default, itemStyles.sub)}
234+
>
235+
<Link to="/deployment/general">Deployment</Link>
236+
</DropdownMenuItem>
237+
)}
238+
{canViewOrganizations && (
239+
<DropdownMenuItem
240+
asChild
241+
className={cn(itemStyles.default, itemStyles.sub)}
242+
>
243+
<Link to="/organizations">
244+
Organizations
245+
<FeatureStageBadge
246+
contentType="beta"
247+
size="sm"
248+
showTooltip={false}
249+
/>
250+
</Link>
251+
</DropdownMenuItem>
252+
)}
253+
{canViewAuditLog && (
254+
<DropdownMenuItem
255+
asChild
256+
className={cn(itemStyles.default, itemStyles.sub)}
257+
>
258+
<Link to="/audit">Audit logs</Link>
259+
</DropdownMenuItem>
260+
)}
261+
{canViewHealth && (
262+
<DropdownMenuItem
263+
asChild
264+
className={cn(itemStyles.default, itemStyles.sub)}
265+
>
266+
<Link to="/health">Healthcheck</Link>
267+
</DropdownMenuItem>
268+
)}
240269
</CollapsibleContent>
241270
</Collapsible>
242271
);

site/src/modules/dashboard/Navbar/Navbar.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const Navbar: FC = () => {
2020
const canViewDeployment = Boolean(permissions.viewDeploymentValues);
2121
const canViewOrganizations =
2222
Boolean(permissions.editAnyOrganization) && showOrganizations;
23-
const canViewAllUsers = Boolean(permissions.viewAllUsers);
2423
const proxyContextValue = useProxy();
2524
const canViewHealth = canViewDeployment;
2625

@@ -33,7 +32,6 @@ export const Navbar: FC = () => {
3332
onSignOut={signOut}
3433
canViewDeployment={canViewDeployment}
3534
canViewOrganizations={canViewOrganizations}
36-
canViewAllUsers={canViewAllUsers}
3735
canViewHealth={canViewHealth}
3836
canViewAuditLog={canViewAuditLog}
3937
proxyContextValue={proxyContextValue}

0 commit comments

Comments
 (0)