feat: remove mui from <AppearanceSettingsPage /> (#22368)

This migrates the content from using `MUI` to the new standard `shadcn`
components on the `<AppearanceSettingsPage />`. Things should work
exactly how they did before, but with a new shinier coat of paint.

| Old | New |
| --- | --- |
| <img width="1019" height="651" alt="APPEARANCE_SETTINGS_OLD"
src="https://github.com/user-attachments/assets/d514ea17-f669-4343-99c1-9c8896ae85d8"
/> | <img width="1019" height="653" alt="APPEARANCE_SETTINGS_NEW"
src="https://github.com/user-attachments/assets/424616af-c975-43fa-bd4a-13d0b0fe136b"
/> |
This commit is contained in:
Jake Howell
2026-02-27 22:29:40 +11:00
committed by GitHub
parent fb154cb60a
commit c6638f5547
3 changed files with 49 additions and 136 deletions
@@ -1,10 +1,8 @@
import { type CSSObject, useTheme } from "@emotion/react";
import Link from "@mui/material/Link";
import type { BannerConfig } from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { EmptyState } from "components/EmptyState/EmptyState";
import { Stack } from "components/Stack/Stack";
import { Link } from "components/Link/Link";
import {
Table,
TableBody,
@@ -27,7 +25,6 @@ interface AnnouncementBannersettingsProps {
export const AnnouncementBannerSettings: FC<
AnnouncementBannersettingsProps
> = ({ isEntitled, announcementBanners, onSubmit }) => {
const theme = useTheme();
const [banners, setBanners] = useState(announcementBanners);
const [editingBannerId, setEditingBannerId] = useState<number | null>(null);
const [deletingBannerId, setDeletingBannerId] = useState<number | null>(null);
@@ -65,29 +62,10 @@ export const AnnouncementBannerSettings: FC<
return (
<>
<div
css={{
borderRadius: 8,
border: `1px solid ${theme.palette.divider}`,
marginTop: 32,
overflow: "hidden",
}}
>
<div className="mt-8 overflow-hidden rounded-lg border border-solid border-border">
<div className="p-6">
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
>
<h3
css={{
fontSize: 20,
margin: 0,
fontWeight: 600,
}}
>
Announcement Banners
</h3>
<div className="flex items-center justify-between gap-4">
<h3 className="m-0 text-xl font-semibold">Announcement Banners</h3>
<Button
disabled={!isEntitled}
onClick={() => addBanner()}
@@ -96,18 +74,12 @@ export const AnnouncementBannerSettings: FC<
<PlusIcon />
New
</Button>
</Stack>
<div
css={{
color: theme.palette.text.secondary,
fontSize: 14,
marginTop: 8,
}}
>
</div>
<div className="mt-2 text-sm text-content-secondary">
Display message banners to all users.
</div>
<div css={[theme.typography.body2 as CSSObject, { paddingTop: 16 }]}>
<div className="pt-4 text-sm">
<Table>
<TableHeader>
<TableRow>
@@ -147,20 +119,14 @@ export const AnnouncementBannerSettings: FC<
</div>
{!isEntitled && (
<footer
css={[
theme.typography.body2 as CSSObject,
{
background: theme.palette.background.paper,
padding: "16px 24px",
},
]}
>
<footer className="bg-surface-secondary px-6 py-4 text-sm">
<div className="text-content-secondary">
<p>
Your license does not include Service Banners.{" "}
<Link href="mailto:sales@coder.com">Contact sales</Link> to
learn more.
<Link href="mailto:sales@coder.com" showExternalIcon={false}>
Contact sales
</Link>{" "}
to learn more.
</p>
</div>
</footer>
@@ -1,5 +1,3 @@
import InputAdornment from "@mui/material/InputAdornment";
import TextField from "@mui/material/TextField";
import type { UpdateAppearanceConfig } from "api/typesGenerated";
import {
Badges,
@@ -7,6 +5,12 @@ import {
PremiumBadge,
} from "components/Badges/Badges";
import { Button } from "components/Button/Button";
import { Input } from "components/Input/Input";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "components/InputGroup/InputGroup";
import { PopoverPaywall } from "components/Paywall/PopoverPaywall";
import {
SettingsHeader,
@@ -98,15 +102,11 @@ export const AppearanceSettingsPageView: FC<
onSubmit={applicationNameForm.handleSubmit}
button={!isEntitled && <Button disabled>Submit</Button>}
>
<TextField
<Input
{...applicationNameFieldHelpers("application_name")}
defaultValue={appearance.application_name}
fullWidth
placeholder='Leave empty to display "Coder".'
disabled={!isEntitled}
inputProps={{
"aria-label": "Application name",
}}
aria-label="Application name"
/>
</Fieldset>
@@ -122,44 +122,28 @@ export const AppearanceSettingsPageView: FC<
onSubmit={logoForm.handleSubmit}
button={!isEntitled && <Button disabled>Submit</Button>}
>
<TextField
{...logoFieldHelpers("logo_url")}
defaultValue={appearance.logo_url}
fullWidth
placeholder="Leave empty to display the Coder logo."
disabled={!isEntitled}
InputProps={{
endAdornment: (
<InputAdornment
position="end"
css={{
width: 24,
height: 24,
"& img": {
maxWidth: "100%",
},
}}
>
<img
alt=""
src={logoForm.values.logo_url}
// This prevent browser to display the ugly error icon if the
// image path is wrong or user didn't finish typing the url
onError={(e) => {
e.currentTarget.style.display = "none";
}}
onLoad={(e) => {
e.currentTarget.style.display = "inline";
}}
/>
</InputAdornment>
),
}}
inputProps={{
"aria-label": "Logo URL",
}}
/>
<InputGroup>
<InputGroupInput
{...logoFieldHelpers("logo_url")}
placeholder="Leave empty to display the Coder logo."
disabled={!isEntitled}
aria-label="Logo URL"
/>
<InputGroupAddon align="inline-end">
<img
alt=""
src={logoForm.values.logo_url}
className="h-6 w-6 max-w-full object-contain"
// Hide broken image icon while users type incomplete URLs.
onError={(e) => {
e.currentTarget.style.display = "none";
}}
onLoad={(e) => {
e.currentTarget.style.display = "inline";
}}
/>
</InputGroupAddon>
</InputGroup>
</Fieldset>
<AnnouncementBannerSettings
@@ -1,4 +1,3 @@
import { type CSSObject, useTheme } from "@emotion/react";
import { Button } from "components/Button/Button";
import type { FC, FormEventHandler, JSX, ReactNode } from "react";
@@ -21,56 +20,20 @@ export const Fieldset: FC<FieldsetProps> = ({
onSubmit,
isSubmitting,
}) => {
const theme = useTheme();
return (
<form
css={{
borderRadius: 8,
border: `1px solid ${theme.palette.divider}`,
marginTop: 32,
overflow: "hidden",
}}
className="mt-8 overflow-hidden rounded-lg border border-solid border-border"
onSubmit={onSubmit}
>
<header css={{ padding: 24 }}>
<div
css={{
fontSize: 20,
margin: 0,
fontWeight: 600,
}}
>
{title}
</div>
<header className="p-6">
<div className="text-xl font-semibold">{title}</div>
{subtitle && (
<div
css={{
color: theme.palette.text.secondary,
fontSize: 14,
marginTop: 8,
}}
>
{subtitle}
</div>
<div className="mt-2 text-sm text-content-secondary">{subtitle}</div>
)}
<div css={[theme.typography.body2 as CSSObject, { paddingTop: 16 }]}>
{children}
</div>
<div className="pt-4 text-sm">{children}</div>
</header>
<footer
css={[
theme.typography.body2 as CSSObject,
{
background: theme.palette.background.paper,
padding: "16px 24px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
},
]}
>
<div css={{ color: theme.palette.text.secondary }}>{validation}</div>
<footer className="flex items-center justify-between bg-surface-secondary px-6 py-4 text-sm">
<div className="text-content-secondary">{validation}</div>
{button || (
<Button type="submit" disabled={isSubmitting}>
Submit