From 80e355862e5c173a29a0b2ecc9323f0b8ea80d3c Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Sat, 1 Aug 2026 10:13:21 +0000 Subject: [PATCH 1/4] refactor(site): demui `` into dropdown --- .../NotificationEvents.stories.tsx | 36 ++-- .../NotificationsPage/NotificationEvents.tsx | 179 +++++++----------- 2 files changed, 86 insertions(+), 129 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.stories.tsx b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.stories.tsx index 26a5f9b991994..202fccf483905 100644 --- a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.stories.tsx @@ -55,40 +55,44 @@ export const WebhookNotConfigured: Story = { }, }; -export const Toggle: Story = { +export const ChangeMethod: Story = { play: async ({ canvasElement }) => { spyOn(API, "updateNotificationTemplateMethod").mockResolvedValue(); const user = userEvent.setup(); const canvas = within(canvasElement); const tmpl = MockSystemNotificationTemplates[4]; const option = await canvas.findByText(tmpl.name); - const li = option.closest("li"); - if (!li) { - throw new Error("Could not find li"); + const row = option.closest("[data-testid='notification-template-row']"); + if (!(row instanceof HTMLElement)) { + throw new Error("Could not find notification template row"); } - const toggleButton = within(li).getByRole("button", { - name: "Webhook", - }); - await user.click(toggleButton); + await user.click( + within(row).getByRole("combobox", { name: "Notification method" }), + ); + await user.click( + await within(document.body).findByRole("option", { name: "Webhook" }), + ); await within(document.body).findByText("Notification method updated."); }, }; -export const ToggleError: Story = { +export const ChangeMethodError: Story = { play: async ({ canvasElement }) => { spyOn(API, "updateNotificationTemplateMethod").mockRejectedValue({}); const user = userEvent.setup(); const canvas = within(canvasElement); const tmpl = MockSystemNotificationTemplates[4]; const option = await canvas.findByText(tmpl.name); - const li = option.closest("li"); - if (!li) { - throw new Error("Could not find li"); + const row = option.closest("[data-testid='notification-template-row']"); + if (!(row instanceof HTMLElement)) { + throw new Error("Could not find notification template row"); } - const toggleButton = within(li).getByRole("button", { - name: "Webhook", - }); - await user.click(toggleButton); + await user.click( + within(row).getByRole("combobox", { name: "Notification method" }), + ); + await user.click( + await within(document.body).findByRole("option", { name: "Webhook" }), + ); await within(document.body).findByText( "Failed to update notification method.", ); diff --git a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx index f524016157b52..022de8eca6443 100644 --- a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx +++ b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx @@ -1,12 +1,4 @@ -import type { Interpolation, Theme } from "@emotion/react"; -import Card from "@mui/material/Card"; -import Divider from "@mui/material/Divider"; -import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import ListItemText, { listItemTextClasses } from "@mui/material/ListItemText"; -import ToggleButton from "@mui/material/ToggleButton"; -import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; -import { type FC, Fragment } from "react"; +import type { FC } from "react"; import { useMutation, useQueryClient } from "react-query"; import { toast } from "sonner"; import { getErrorDetail, getErrorMessage } from "#/api/errors"; @@ -18,10 +10,12 @@ import type { DeploymentValues } from "#/api/typesGenerated"; import { Alert } from "#/components/Alert/Alert"; import { Button } from "#/components/Button/Button"; import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "#/components/Tooltip/Tooltip"; + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "#/components/Select/Select"; import { castNotificationMethod, methodIcons, @@ -108,36 +102,34 @@ export const NotificationEvents: FC = ({ )} {Object.entries(templatesByGroup).map(([group, templates]) => ( - - - - - +
+
+ {group} +
- {templates.map((tpl, i) => { - const value = castNotificationMethod(tpl.method || defaultMethod); - const isLastItem = i === templates.length - 1; + {templates.map((tpl) => { + const value = castNotificationMethod(tpl.method || defaultMethod); - return ( - - - - - - {!isLastItem && } - - ); - })} - - + return ( +
+ {tpl.name} + +
+ ); + })} +
))} ); @@ -150,14 +142,14 @@ function requiredFieldsArePresent( return fields.every((field) => Boolean(obj[field])); } -type MethodToggleGroupProps = { +type MethodSelectProps = { templateId: string; options: NotificationMethod[]; value: NotificationMethod; canEdit: boolean; }; -const MethodToggleGroup: FC = ({ +const MethodSelect: FC = ({ value, options, templateId, @@ -168,16 +160,14 @@ const MethodToggleGroup: FC = ({ updateNotificationTemplateMethod(templateId, queryClient), ); + const SelectedIcon = methodIcons[value]; + return ( - { - if (!method) { + onValueChange={async (method) => { + if (method === value) { return; } try { @@ -195,68 +185,31 @@ const MethodToggleGroup: FC = ({ } }} > - {options.map((method) => { - const Icon = methodIcons[method]; - const label = methodLabels[method]; - return ( - - - { - // Retain the value if the user clicks the same button, ensuring - // at least one value remains selected. - if (method === value) { - e.preventDefault(); - e.stopPropagation(); - return; - } - }} - > - - - - {label} - - ); - })} - + + + + + {methodLabels[value]} + + + + + {options.map((method) => { + const Icon = methodIcons[method]; + const label = methodLabels[method]; + return ( + + + + {label} + + + ); + })} + + ); }; - -const styles = { - listHeader: (theme) => ({ - background: theme.palette.background.paper, - borderBottom: `1px solid ${theme.palette.divider}`, - }), - listItemText: { - [`& .${listItemTextClasses.primary}`]: { - fontSize: 14, - fontWeight: 500, - }, - [`& .${listItemTextClasses.secondary}`]: { - fontSize: 14, - }, - }, - toggleGroup: (theme) => ({ - border: `1px solid ${theme.palette.divider}`, - borderRadius: 4, - }), - toggleButton: (theme) => ({ - border: 0, - borderRadius: 4, - fontSize: 16, - padding: "4px 8px", - color: theme.palette.text.disabled, - - "&:hover": { - color: theme.palette.text.primary, - }, - - "& svg": { - fontSize: "inherit", - }, - }), -} as Record>; From c694ebda306bbf6cb7aa47838fc77dc1d301838c Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Sat, 1 Aug 2026 20:24:33 +1000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A4=96=20fix(site):=20include=20templ?= =?UTF-8?q?ate=20name=20in=20notification=20method=20select=20accessible?= =?UTF-8?q?=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsPage/NotificationEvents.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx index 022de8eca6443..fb48235f39137 100644 --- a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx +++ b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx @@ -112,6 +112,7 @@ export const NotificationEvents: FC = ({ {templates.map((tpl) => { const value = castNotificationMethod(tpl.method || defaultMethod); + const labelId = `notification-template-${tpl.id}`; return (
= ({ data-testid="notification-template-row" className="flex items-center justify-between gap-3 px-4 py-3 border-0 [&:not(:last-child)]:border-b border-solid" > - {tpl.name} + + {tpl.name} + = ({ value, options, templateId, + labelId, canEdit, }) => { const queryClient = useQueryClient(); @@ -187,6 +193,8 @@ const MethodSelect: FC = ({ > From 876659cde2f61b8cd63d995b7ae7930d3921d121 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Sat, 1 Aug 2026 20:25:20 +1000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A4=96=20fix(site):=20describe=20noti?= =?UTF-8?q?fication=20method=20select=20with=20template=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsPage/NotificationEvents.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx index fb48235f39137..7e8c406bafaf9 100644 --- a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx +++ b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx @@ -193,8 +193,7 @@ const MethodSelect: FC = ({ > From ad735efd99d1c5d76274651c0e32de7e98a8f984 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Sat, 1 Aug 2026 20:26:08 +1000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=A4=96=20fix(site):=20give=20each=20n?= =?UTF-8?q?otification=20method=20select=20a=20template-specific=20accessi?= =?UTF-8?q?ble=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsPage/NotificationEvents.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx index 7e8c406bafaf9..a0000ea57224b 100644 --- a/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx +++ b/site/src/pages/DeploymentSettingsPage/NotificationsPage/NotificationEvents.tsx @@ -112,7 +112,6 @@ export const NotificationEvents: FC = ({ {templates.map((tpl) => { const value = castNotificationMethod(tpl.method || defaultMethod); - const labelId = `notification-template-${tpl.id}`; return (
= ({ data-testid="notification-template-row" className="flex items-center justify-between gap-3 px-4 py-3 border-0 [&:not(:last-child)]:border-b border-solid" > - - {tpl.name} - + {tpl.name} = ({ value, options, templateId, - labelId, + templateName, canEdit, }) => { const queryClient = useQueryClient(); @@ -192,8 +189,7 @@ const MethodSelect: FC = ({ }} >