Skip to content

Commit 8a0094c

Browse files
authored
feat: add redesigned organization settings sidebar (coder#15932)
resolves coder/internal#173, coder/internal#175 This PR does the following 1. Updates the left sidebar for organizations to use a dropdown to select the organization 2. Move the create organization button inside the dropdown 3. Update the design of the create organization page 4. Do not display the organization in the dropdown if there is only 1 org to display Figma: https://www.figma.com/design/OR75XeUI0Z3ksqt1mHsNQw/Dashboard-v1?node-id=139-1380&m=dev The loading state for the save button in the create organization form will be handled separately after coder#14978 is completed. Note: Since the dropdown is based off the cmdk component, navigation in the dropdown is handled by the arrow keys, https://cmdk.paco.me/ <img width="560" alt="Screenshot 2025-01-03 at 21 11 26" src="https://github.com/user-attachments/assets/ff6e61ab-c8d4-4f97-b603-306492e9bfec" /> <img width="641" alt="Screenshot 2025-01-03 at 21 11 39" src="https://github.com/user-attachments/assets/fedb28e0-9ef3-4b0f-8665-06215338f351" /> <img width="1178" alt="Screenshot 2025-01-03 at 21 12 05" src="https://github.com/user-attachments/assets/ee672533-2689-4b2e-a7bf-471ea72e1095" /> <img width="1177" alt="Screenshot 2025-01-03 at 21 12 39" src="https://github.com/user-attachments/assets/f13824a6-2581-4bff-b5b6-2024c2e145a4" />
1 parent dcf0337 commit 8a0094c

18 files changed

+461
-349
lines changed

Diff for: pnpm-lock.yaml

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: site/e2e/tests/organizationGroups.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ test("create group", async ({ page }) => {
2323
await page.goto(`/organizations/${org.name}`);
2424

2525
// Navigate to groups page
26-
await page.getByText("Groups").click();
26+
await page.getByRole("link", { name: "Groups" }).click();
2727
await expect(page).toHaveTitle(`Groups - Org ${org.name} - Coder`);
2828

2929
// Create a new group

Diff for: site/e2e/tests/organizationMembers.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test("add and remove organization member", async ({ page }) => {
2121
const { displayName } = await createOrganization(page);
2222

2323
// Navigate to members page
24-
await page.getByText("Members").click();
24+
await page.getByRole("link", { name: "Members" }).click();
2525
await expect(page).toHaveTitle(`Members - ${displayName} - Coder`);
2626

2727
// Add a user to the org

Diff for: site/e2e/tests/organizations.spec.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,25 @@ test("create and delete organization", async ({ page }) => {
2929
await expectUrl(page).toHavePathName(`/organizations/${name}`);
3030
await expect(page.getByText("Organization created.")).toBeVisible();
3131

32+
await page.goto(`/organizations/${name}/settings`, {
33+
waitUntil: "domcontentloaded",
34+
});
35+
3236
const newName = randomName();
3337
await page.getByLabel("Slug").fill(newName);
3438
await page.getByLabel("Description").fill(`Org description ${newName}`);
3539
await page.getByRole("button", { name: /save/i }).click();
3640

3741
// Expect to be redirected when renaming the organization
38-
await expectUrl(page).toHavePathName(`/organizations/${newName}`);
42+
await expectUrl(page).toHavePathName(`/organizations/${newName}/settings`);
3943
await expect(page.getByText("Organization settings updated.")).toBeVisible();
4044

45+
await page.goto(`/organizations/${newName}/settings`, {
46+
waitUntil: "domcontentloaded",
47+
});
48+
// Expect to be redirected when renaming the organization
49+
await expectUrl(page).toHavePathName(`/organizations/${newName}/settings`);
50+
4151
await page.getByRole("button", { name: "Delete this organization" }).click();
4252
const dialog = page.getByTestId("dialog");
4353
await dialog.getByLabel("Name").fill(newName);

Diff for: site/src/components/Command/Command.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const CommandList = forwardRef<
6969
>(({ className, ...props }, ref) => (
7070
<CommandPrimitive.List
7171
ref={ref}
72-
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
72+
className={cn("max-h-96 overflow-y-auto overflow-x-hidden", className)}
7373
{...props}
7474
/>
7575
));
@@ -92,7 +92,7 @@ export const CommandGroup = forwardRef<
9292
<CommandPrimitive.Group
9393
ref={ref}
9494
className={cn(
95-
`overflow-hidden p-1 text-content-primary
95+
`overflow-hidden p-2 text-content-primary
9696
[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs
9797
[&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-content-secondary`,
9898
className,
@@ -119,7 +119,7 @@ export const CommandItem = forwardRef<
119119
<CommandPrimitive.Item
120120
ref={ref}
121121
className={cn(
122-
`relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none
122+
`relative flex cursor-default gap-2 select-none text-content-secondary items-center rounded-sm px-2 py-2 text-sm font-medium outline-none
123123
data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50
124124
data-[selected=true]:bg-surface-secondary data-[selected=true]:text-content-primary
125125
[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`,

Diff for: site/src/components/Popover/Popover.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const PopoverContent = forwardRef<
2424
align={align}
2525
sideOffset={sideOffset}
2626
className={cn(
27-
`z-50 w-72 rounded-md border border-solid bg-surface-primary p-4
27+
`z-50 w-72 rounded-md border border-solid bg-surface-primary
2828
text-content-primary shadow-md outline-none
2929
data-[state=open]:animate-in data-[state=closed]:animate-out
3030
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0

Diff for: site/src/components/Sidebar/Sidebar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const SettingsSidebarNavItem: FC<SettingsSidebarNavItemProps> = ({
6767
to={href}
6868
className={({ isActive }) =>
6969
cn(
70-
"relative text-sm text-content-secondary no-underline font-medium py-2 px-3 hover:bg-surface-secondary rounded-md transition ease-in-out duration-150 ",
70+
"relative text-sm text-content-secondary no-underline font-medium py-2 px-3 hover:bg-surface-secondary rounded-md transition ease-in-out duration-150",
7171
{
7272
"font-semibold text-content-primary": isActive,
7373
},

Diff for: site/src/modules/management/DeploymentSettingsLayout.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ const DeploymentSettingsLayout: FC = () => {
3939
</BreadcrumbList>
4040
</Breadcrumb>
4141
<hr className="h-px border-none bg-border" />
42-
<div className="px-6 max-w-screen-2xl">
43-
<div className="flex flex-row gap-12 py-10">
42+
<div className="px-10 max-w-screen-2xl">
43+
<div className="flex flex-row gap-28 py-10">
4444
<DeploymentSidebar />
4545
<main css={{ flexGrow: 1 }}>
4646
<Suspense fallback={<Loader />}>

Diff for: site/src/modules/management/DeploymentSidebarView.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { withDashboardProvider } from "testHelpers/storybook";
44
import { DeploymentSidebarView } from "./DeploymentSidebarView";
55

66
const meta: Meta<typeof DeploymentSidebarView> = {
7-
title: "modules/management/SidebarView",
7+
title: "modules/management/DeploymentSidebarView",
88
component: DeploymentSidebarView,
99
decorators: [withDashboardProvider],
1010
parameters: { showOrganizations: true },

Diff for: site/src/modules/management/OrganizationSettingsLayout.tsx

+6-15
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { RequirePermission } from "contexts/auth/RequirePermission";
1515
import { useDashboard } from "modules/dashboard/useDashboard";
1616
import { type FC, Suspense, createContext, useContext } from "react";
1717
import { Outlet, useParams } from "react-router-dom";
18-
import { OrganizationSidebar } from "./OrganizationSidebar";
1918

2019
export const OrganizationSettingsContext = createContext<
2120
OrganizationSettingsValue | undefined
@@ -82,13 +81,10 @@ const OrganizationSettingsLayout: FC = () => {
8281
</BreadcrumbItem>
8382
<BreadcrumbSeparator />
8483
<BreadcrumbItem>
85-
<BreadcrumbLink
86-
href="/organizations"
87-
className="flex items-center gap-2"
88-
>
84+
<BreadcrumbPage className="flex items-center gap-2">
8985
Organizations
9086
<FeatureStageBadge contentType="beta" size="sm" />
91-
</BreadcrumbLink>
87+
</BreadcrumbPage>
9288
</BreadcrumbItem>
9389
{organization && (
9490
<>
@@ -109,15 +105,10 @@ const OrganizationSettingsLayout: FC = () => {
109105
</BreadcrumbList>
110106
</Breadcrumb>
111107
<hr className="h-px border-none bg-border" />
112-
<div className="px-6 max-w-screen-2xl">
113-
<div className="flex flex-row gap-12 py-10">
114-
<OrganizationSidebar />
115-
<main css={{ flexGrow: 1 }}>
116-
<Suspense fallback={<Loader />}>
117-
<Outlet />
118-
</Suspense>
119-
</main>
120-
</div>
108+
<div className="px-10 max-w-screen-2xl">
109+
<Suspense fallback={<Loader />}>
110+
<Outlet />
111+
</Suspense>
121112
</div>
122113
</div>
123114
</OrganizationSettingsContext.Provider>

Diff for: site/src/modules/management/OrganizationSidebar.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ export const OrganizationSidebar: FC = () => {
4747
return canEditOrganization(org.permissions);
4848
});
4949

50+
const organization = editableOrgs?.find((o) => o.name === organizationName);
51+
5052
return (
5153
<OrganizationSidebarView
52-
activeOrganizationName={organizationName}
54+
activeOrganization={organization}
5355
organizations={editableOrgs}
5456
permissions={permissions}
5557
/>
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Loader } from "components/Loader/Loader";
2+
import { type FC, Suspense } from "react";
3+
import { Outlet } from "react-router-dom";
4+
import { OrganizationSidebar } from "./OrganizationSidebar";
5+
6+
const OrganizationSidebarLayout: FC = () => {
7+
return (
8+
<div className="flex flex-row gap-28 py-10">
9+
<OrganizationSidebar />
10+
<main css={{ flexGrow: 1 }}>
11+
<Suspense fallback={<Loader />}>
12+
<Outlet />
13+
</Suspense>
14+
</main>
15+
</div>
16+
);
17+
};
18+
19+
export default OrganizationSidebarLayout;

0 commit comments

Comments
 (0)