Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cb6facb

Browse files
refactor: use the new button component on forms and dialogs (#16050)
This is a significant PR that will impact many parts of the UI, so I’d like to ask you, @jaaydenh, for a very thorough review of the Storybook stories on Chromatic. I know it’s a bit of a hassle with around 180 stories affected, but it’s all for a good cause 💪 Main changes: - Update the `Button` component to match the [new buttons design](https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=3-1756&p=f&m=dev). - Update forms and dialogs to use the new `Button` component. Related to #14978
1 parent 289338f commit cb6facb

File tree

54 files changed

+434
-491
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+434
-491
lines changed

site/e2e/helpers.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export const createWorkspace = async (
147147
await popup.waitForSelector("text=You are now authenticated.");
148148
}
149149

150-
await page.getByTestId("form-submit").click();
150+
await page.getByRole("button", { name: /create workspace/i }).click();
151151

152152
const user = currentUser(page);
153153

@@ -276,7 +276,7 @@ export const createTemplate = async (
276276

277277
const name = randomName();
278278
await page.getByLabel("Name *").fill(name);
279-
await page.getByTestId("form-submit").click();
279+
await page.getByRole("button", { name: /save/i }).click();
280280
await expectUrl(page).toHavePathName(
281281
organizationsEnabled
282282
? `/templates/${orgName}/${name}/files`
@@ -298,7 +298,7 @@ export const createGroup = async (page: Page): Promise<string> => {
298298

299299
const name = randomName();
300300
await page.getByLabel("Name", { exact: true }).fill(name);
301-
await page.getByTestId("form-submit").click();
301+
await page.getByRole("button", { name: /save/i }).click();
302302
await expectUrl(page).toHavePathName(`/groups/${name}`);
303303
return name;
304304
};
@@ -982,7 +982,7 @@ export const updateTemplateSettings = async (
982982
await page.getByLabel(labelText, { exact: true }).fill(value);
983983
}
984984

985-
await page.getByTestId("form-submit").click();
985+
await page.getByRole("button", { name: /save/i }).click();
986986

987987
const name = templateSettingValues.name ?? templateName;
988988
await expectUrl(page).toHavePathNameEndingWith(`/${name}`);
@@ -1003,7 +1003,7 @@ export const updateWorkspace = async (
10031003
await page.getByTestId("confirm-button").click();
10041004

10051005
await fillParameters(page, richParameters, buildParameters);
1006-
await page.getByTestId("form-submit").click();
1006+
await page.getByRole("button", { name: /update parameters/i }).click();
10071007

10081008
await page.waitForSelector("*[data-testid='build-status'] >> text=Running", {
10091009
state: "visible",
@@ -1024,7 +1024,7 @@ export const updateWorkspaceParameters = async (
10241024
);
10251025

10261026
await fillParameters(page, richParameters, buildParameters);
1027-
await page.getByTestId("form-submit").click();
1027+
await page.getByRole("button", { name: /submit and restart/i }).click();
10281028

10291029
await page.waitForSelector("*[data-testid='build-status'] >> text=Running", {
10301030
state: "visible",
@@ -1091,7 +1091,7 @@ export async function createUser(
10911091
// as the label for the currently active option.
10921092
const passwordField = page.locator("input[name=password]");
10931093
await passwordField.fill(password);
1094-
await page.getByRole("button", { name: "Create user" }).click();
1094+
await page.getByRole("button", { name: /save/i }).click();
10951095
await expect(page.getByText("Successfully created user.")).toBeVisible();
10961096

10971097
await expect(page).toHaveTitle("Users - Coder");
@@ -1123,7 +1123,7 @@ export async function createOrganization(page: Page): Promise<{
11231123
const description = `Org description ${name}`;
11241124
await page.getByLabel("Description").fill(description);
11251125
await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png");
1126-
await page.getByRole("button", { name: "Submit" }).click();
1126+
await page.getByRole("button", { name: /save/i }).click();
11271127

11281128
await expectUrl(page).toHavePathName(`/organizations/${name}`);
11291129
await expect(page.getByText("Organization created.")).toBeVisible();

site/e2e/tests/deployment/idpOrgSync.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ test.describe("IdpOrgSyncPage", () => {
6767
const syncField = page.getByRole("textbox", {
6868
name: "Organization sync field",
6969
});
70-
const saveButton = page.getByRole("button", { name: "Save" }).first();
70+
const saveButton = page.getByRole("button", { name: /save/i }).first();
7171

7272
await expect(saveButton).toBeDisabled();
7373

7474
await syncField.fill("test-field");
7575
await expect(saveButton).toBeEnabled();
7676

77-
await page.getByRole("button", { name: "Save" }).click();
77+
await page.getByRole("button", { name: /save/i }).click();
7878

7979
await expect(
8080
page.getByText("Organization sync settings updated."),

site/e2e/tests/groups/createGroup.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test("create group", async ({ page, baseURL }) => {
2727
await page.getByLabel("Name", { exact: true }).fill(groupValues.name);
2828
await page.getByLabel("Display Name").fill(groupValues.displayName);
2929
await page.getByLabel("Avatar URL").fill(groupValues.avatarURL);
30-
await page.getByRole("button", { name: "Submit" }).click();
30+
await page.getByRole("button", { name: /save/i }).click();
3131

3232
await expect(page).toHaveTitle(`${groupValues.displayName} - Coder`);
3333
await expect(page.getByText(groupValues.displayName)).toBeVisible();

site/e2e/tests/organizationGroups.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ test("create group", async ({ page }) => {
3434
const displayName = `Group ${name}`;
3535
await page.getByLabel("Display Name").fill(displayName);
3636
await page.getByLabel("Avatar URL").fill("/emojis/1f60d.png");
37-
await page.getByRole("button", { name: "Submit" }).click();
37+
await page.getByRole("button", { name: /save/i }).click();
3838

3939
await expectUrl(page).toHavePathName(
4040
`/organizations/${org.name}/groups/${name}`,
@@ -91,7 +91,7 @@ test("change quota settings", async ({ page }) => {
9191

9292
// Update Quota
9393
await page.getByLabel("Quota Allowance").fill("100");
94-
await page.getByRole("button", { name: "Submit" }).click();
94+
await page.getByRole("button", { name: /save/i }).click();
9595

9696
// We should get sent back to the group page afterwards
9797
expectUrl(page).toHavePathName(

site/e2e/tests/organizations.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ test("create and delete organization", async ({ page }) => {
2323
await page.getByLabel("Display name").fill(`Org ${name}`);
2424
await page.getByLabel("Description").fill(`Org description ${name}`);
2525
await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png");
26-
await page.getByRole("button", { name: "Submit" }).click();
26+
await page.getByRole("button", { name: /save/i }).click();
2727

2828
// Expect to be redirected to the new organization
2929
await expectUrl(page).toHavePathName(`/organizations/${name}`);
@@ -32,7 +32,7 @@ test("create and delete organization", async ({ page }) => {
3232
const newName = randomName();
3333
await page.getByLabel("Slug").fill(newName);
3434
await page.getByLabel("Description").fill(`Org description ${newName}`);
35-
await page.getByRole("button", { name: "Submit" }).click();
35+
await page.getByRole("button", { name: /save/i }).click();
3636

3737
// Expect to be redirected when renaming the organization
3838
await expectUrl(page).toHavePathName(`/organizations/${newName}`);

site/e2e/tests/organizations/customRoles/customRoles.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ test.describe("CustomRolesPage", () => {
8787
await expect(organizationMemberCheckbox).toBeVisible();
8888
await organizationMemberCheckbox.click();
8989

90-
const saveButton = page.getByRole("button", { name: "Save" }).first();
90+
const saveButton = page.getByRole("button", { name: /save/i }).first();
9191
await expect(saveButton).toBeVisible();
9292
await saveButton.click();
9393

site/e2e/tests/templates/updateTemplateSchedule.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ test("update template schedule settings without override other settings", async
3636
waitUntil: "domcontentloaded",
3737
});
3838
await page.getByLabel("Default autostop (hours)").fill("48");
39-
await page.getByRole("button", { name: "Submit" }).click();
39+
await page.getByRole("button", { name: /save/i }).click();
4040
await expect(page.getByText("Template updated successfully")).toBeVisible();
4141

4242
const updatedTemplate = await API.getTemplate(template.id);

site/e2e/tests/updateTemplate.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ test("require latest version", async ({ page }) => {
6767
await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`);
6868
let checkbox = await page.waitForSelector("#require_active_version");
6969
await checkbox.click();
70-
await page.getByTestId("form-submit").click();
70+
await page.getByRole("button", { name: /save/i }).click();
7171

7272
await page.goto(`/templates/${templateName}/settings`, {
7373
waitUntil: "domcontentloaded",
+49-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import { Trash } from "lucide-react";
2+
import { PlusIcon } from "lucide-react";
33
import { Button } from "./Button";
44

55
const meta: Meta<typeof Button> = {
@@ -8,7 +8,7 @@ const meta: Meta<typeof Button> = {
88
args: {
99
children: (
1010
<>
11-
<Trash />
11+
<PlusIcon />
1212
Button
1313
</>
1414
),
@@ -20,34 +20,41 @@ type Story = StoryObj<typeof Button>;
2020

2121
export const Default: Story = {};
2222

23-
export const Outline: Story = {
23+
export const DefaultDisabled: Story = {
2424
args: {
25-
variant: "outline",
25+
disabled: true,
2626
},
2727
};
2828

29-
export const Subtle: Story = {
29+
export const DefaultSmall: Story = {
3030
args: {
31-
variant: "subtle",
31+
size: "sm",
3232
},
3333
};
3434

35-
export const Warning: Story = {
35+
export const Outline: Story = {
3636
args: {
37-
variant: "warning",
37+
variant: "outline",
3838
},
3939
};
4040

41-
export const DefaultDisabled: Story = {
41+
export const OutlineDisabled: Story = {
4242
args: {
43+
variant: "outline",
4344
disabled: true,
4445
},
4546
};
4647

47-
export const OutlineDisabled: Story = {
48+
export const OutlineSmall: Story = {
4849
args: {
4950
variant: "outline",
50-
disabled: true,
51+
size: "sm",
52+
},
53+
};
54+
55+
export const Subtle: Story = {
56+
args: {
57+
variant: "subtle",
5158
},
5259
};
5360

@@ -58,23 +65,51 @@ export const SubtleDisabled: Story = {
5865
},
5966
};
6067

68+
export const SubtleSmall: Story = {
69+
args: {
70+
variant: "subtle",
71+
size: "sm",
72+
},
73+
};
74+
75+
export const Destructive: Story = {
76+
args: {
77+
variant: "destructive",
78+
children: "Delete",
79+
},
80+
};
81+
82+
export const DestructiveDisabled: Story = {
83+
args: {
84+
...Destructive.args,
85+
disabled: true,
86+
},
87+
};
88+
89+
export const DestructiveSmall: Story = {
90+
args: {
91+
...Destructive.args,
92+
size: "sm",
93+
},
94+
};
95+
6196
export const IconButtonDefault: Story = {
6297
args: {
6398
variant: "default",
64-
children: <Trash />,
99+
children: <PlusIcon />,
65100
},
66101
};
67102

68103
export const IconButtonOutline: Story = {
69104
args: {
70105
variant: "outline",
71-
children: <Trash />,
106+
children: <PlusIcon />,
72107
},
73108
};
74109

75110
export const IconButtonSubtle: Story = {
76111
args: {
77112
variant: "subtle",
78-
children: <Trash />,
113+
children: <PlusIcon />,
79114
},
80115
};

site/src/components/Button/Button.tsx

+9-14
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,33 @@ import { forwardRef } from "react";
88
import { cn } from "utils/cn";
99

1010
export const buttonVariants = cva(
11-
`inline-flex items-center justify-center gap-2 whitespace-nowrap
11+
`inline-flex items-center justify-center gap-1 whitespace-nowrap
1212
border-solid rounded-md transition-colors
13-
text-sm font-semibold font-medium cursor-pointer
13+
text-sm font-semibold font-medium cursor-pointer no-underline
1414
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link
1515
disabled:pointer-events-none disabled:text-content-disabled
16-
[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0
17-
px-3 py-2`,
16+
[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:p-[2px]`,
1817
{
1918
variants: {
2019
variant: {
2120
default:
22-
"bg-surface-invert-primary text-content-invert hover:bg-surface-invert-secondary border-none disabled:bg-surface-secondary",
21+
"bg-surface-invert-primary text-content-invert hover:bg-surface-invert-secondary border-none disabled:bg-surface-secondary font-semibold",
2322
outline:
2423
"border border-border-default text-content-primary bg-transparent hover:bg-surface-secondary",
2524
subtle:
2625
"border-none bg-transparent text-content-secondary hover:text-content-primary",
27-
warning:
28-
"border border-border-error text-content-primary bg-surface-error hover:bg-transparent",
29-
ghost:
30-
"text-content-primary bg-transparent border-0 hover:bg-surface-secondary",
26+
destructive:
27+
"border border-border-destructive text-content-primary bg-surface-destructive hover:bg-transparent disabled:bg-transparent disabled:text-content-disabled font-semibold",
3128
},
3229

3330
size: {
34-
lg: "h-10",
35-
default: "h-9",
36-
sm: "h-8 px-2 py-1.5 text-xs",
37-
icon: "h-10 w-10",
31+
lg: "h-10 px-3 py-2 [&_svg]:size-icon-lg",
32+
sm: "h-[30px] px-2 py-1.5 text-xs [&_svg]:size-icon-sm",
3833
},
3934
},
4035
defaultVariants: {
4136
variant: "default",
42-
size: "default",
37+
size: "lg",
4338
},
4439
},
4540
);

site/src/components/Dialogs/DeleteDialog/DeleteDialog.stories.tsx

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { action } from "@storybook/addon-actions";
22
import type { Meta, StoryObj } from "@storybook/react";
3+
import { userEvent } from "@storybook/test";
4+
import { within } from "@testing-library/react";
35
import { DeleteDialog } from "./DeleteDialog";
46

57
const meta: Meta<typeof DeleteDialog> = {
@@ -19,12 +21,28 @@ export default meta;
1921

2022
type Story = StoryObj<typeof DeleteDialog>;
2123

22-
const Example: Story = {};
24+
export const Idle: Story = {};
25+
26+
export const FilledSuccessfully: Story = {
27+
play: async ({ canvasElement }) => {
28+
const user = userEvent.setup();
29+
const body = within(canvasElement.ownerDocument.body);
30+
const input = await body.findByLabelText("Name of the foo to delete");
31+
await user.type(input, "MyFoo");
32+
},
33+
};
34+
35+
export const FilledWrong: Story = {
36+
play: async ({ canvasElement }) => {
37+
const user = userEvent.setup();
38+
const body = within(canvasElement.ownerDocument.body);
39+
const input = await body.findByLabelText("Name of the foo to delete");
40+
await user.type(input, "InvalidFooName");
41+
},
42+
};
2343

2444
export const Loading: Story = {
2545
args: {
2646
confirmLoading: true,
2747
},
2848
};
29-
30-
export { Example as DeleteDialog };

0 commit comments

Comments
 (0)