feat: de-mui <Skeleton /> component (#24278)

Replaces remaining MUI `<Skeleton />` usages with our shared `Skeleton`
component to keep loading states consistent and reduce MUI/Emotion
coupling. Also cleans up a few adjacent touched styles while preserving
existing behavior.

- Replace `@mui/material/Skeleton` with `#/components/Skeleton/Skeleton`
- Remove a few MUI-specific skeleton patterns at callsites
- De-mui adjacent Emotion styles
- Simplify filter layout handling with class-based responsive wrapping
This commit is contained in:
Jake Howell
2026-04-13 22:50:01 +10:00
committed by GitHub
parent 39ceb8cfe3
commit 58c6855c59
18 changed files with 130 additions and 343 deletions
+16 -29
View File
@@ -1,8 +1,12 @@
import { useTheme } from "@emotion/react";
import Skeleton, { type SkeletonProps } from "@mui/material/Skeleton";
import type { Breakpoint } from "@mui/system/createTheme";
import { ExternalLinkIcon, SlidersHorizontal } from "lucide-react";
import { type FC, type ReactNode, useEffect, useRef, useState } from "react";
import {
type ComponentProps,
type FC,
type ReactNode,
useEffect,
useRef,
useState,
} from "react";
import {
getValidationErrorMessage,
hasError,
@@ -19,7 +23,9 @@ import {
DropdownMenuTrigger,
} from "#/components/DropdownMenu/DropdownMenu";
import { SearchField } from "#/components/SearchField/SearchField";
import { Skeleton, type SkeletonProps } from "#/components/Skeleton/Skeleton";
import { useDebouncedFunction } from "#/hooks/debounce";
import { cn } from "#/utils/cn";
type PresetFilter = {
name: string;
@@ -132,13 +138,9 @@ const stringifyFilter = (filterValue: FilterValues): string => {
const BaseSkeleton: FC<SkeletonProps> = ({ children, ...skeletonProps }) => {
return (
<Skeleton
variant="rectangular"
height={36}
{...skeletonProps}
css={(theme) => ({
backgroundColor: theme.palette.background.paper,
borderRadius: "6px",
})}
className="bg-surface-tertiary rounded-md w-52"
>
{children}
</Skeleton>
@@ -149,7 +151,7 @@ export const MenuSkeleton: FC = () => {
return <BaseSkeleton className="min-w-[200px] shrink-0" />;
};
type FilterProps = {
type FilterProps = ComponentProps<"div"> & {
filter: ReturnType<typeof useFilter>;
optionsSkeleton: ReactNode;
isLoading: boolean;
@@ -159,13 +161,6 @@ type FilterProps = {
error?: unknown;
options?: ReactNode;
presets: PresetFilter[];
/**
* The CSS media query breakpoint that defines when the UI will try
* displaying all options on one row, regardless of the number of options
* present
*/
singleRowBreakpoint?: Breakpoint;
};
export const Filter: FC<FilterProps> = ({
@@ -178,9 +173,9 @@ export const Filter: FC<FilterProps> = ({
learnMoreLabel2,
learnMoreLink2,
presets,
singleRowBreakpoint = "lg",
className,
...props
}) => {
const theme = useTheme();
// Storing local copy of the filter query so that it can be updated more
// aggressively without re-renders rippling out to the rest of the app every
// single time. Exists for performance reasons - not really a good way to
@@ -205,16 +200,8 @@ export const Filter: FC<FilterProps> = ({
return (
<div
css={{
display: "flex",
gap: 8,
marginBottom: 16,
flexWrap: "wrap",
[theme.breakpoints.up(singleRowBreakpoint)]: {
flexWrap: "nowrap",
},
}}
className={cn("flex gap-2 flex-wrap lg:flex-nowrap mb-4", className)}
{...props}
>
{isLoading ? (
<>
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import type { FC } from "react";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { cn } from "#/utils/cn";
type PaginationHeaderProps = {
paginationUnitLabel: string;
@@ -22,23 +22,15 @@ export const PaginationAmount: FC<PaginationHeaderProps> = ({
countIsCapped,
className,
}) => {
const theme = useTheme();
return (
<div
css={{
display: "flex",
flexFlow: "row nowrap",
alignItems: "center",
margin: 0,
fontSize: "13px",
color: theme.palette.text.secondary,
height: "36px", // The size of a small button
"& strong": {
color: theme.palette.text.primary,
},
}}
className={className}
className={cn(
"flex flex-row flex-nowrap items-center m-0",
"text-[13px] text-content-secondary",
"h-9", // The size of a small button
"[&_strong]:text-content-primary",
className,
)}
>
{totalRecords !== undefined ? (
<>
+1 -1
View File
@@ -18,7 +18,7 @@ const skeletonVariants = cva("bg-surface-tertiary animate-pulse", {
},
});
type SkeletonProps = React.ComponentProps<"div"> &
export type SkeletonProps = React.ComponentProps<"div"> &
VariantProps<typeof skeletonVariants> & {
/** Width in pixels (number) or any CSS value (string). */
width?: number | string;
@@ -1,4 +1,3 @@
import Skeleton from "@mui/material/Skeleton";
import { type FC, useState } from "react";
import { Link } from "react-router";
import { toast } from "sonner";
@@ -16,6 +15,7 @@ import {
DropdownMenuTrigger,
} from "#/components/DropdownMenu/DropdownMenu";
import { Latency } from "#/components/Latency/Latency";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import type { ProxyContextValue } from "#/contexts/ProxyContext";
import { useAuthenticated } from "#/hooks/useAuthenticated";
import { sortProxiesByLatency } from "./proxyUtils";
@@ -1,4 +1,3 @@
import Skeleton from "@mui/material/Skeleton";
import { Container, ExternalLinkIcon } from "lucide-react";
import type { FC } from "react";
import { useMutation, useQueryClient } from "react-query";
@@ -26,6 +25,7 @@ import {
DialogHeader,
DialogTitle,
} from "#/components/Dialog/Dialog";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { Spinner } from "#/components/Spinner/Spinner";
import {
Tooltip,
@@ -374,18 +374,8 @@ export const AgentDevcontainerCard: FC<AgentDevcontainerCardProps> = ({
{showSubAgentAppsPlaceholders && (
<section className={appsClasses}>
<Skeleton
width={80}
height={32}
variant="rectangular"
className="rounded"
/>
<Skeleton
width={110}
height={32}
variant="rectangular"
className="rounded"
/>
<Skeleton width={80} height={32} className="rounded" />
<Skeleton width={110} height={32} className="rounded" />
</section>
)}
</div>
+7 -7
View File
@@ -1,4 +1,3 @@
import Skeleton from "@mui/material/Skeleton";
import dayjs from "dayjs";
import {
type FC,
@@ -15,6 +14,7 @@ import type {
WorkspaceAgent,
WorkspaceAgentMetadata,
} from "#/api/typesGenerated";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { Stack } from "#/components/Stack/Stack";
import {
Tooltip,
@@ -141,18 +141,18 @@ const AgentMetadataSkeleton: FC = () => {
return (
<Stack alignItems="baseline" direction="row" spacing={6}>
<div className="leading-relaxed flex flex-col overflow-visible flex-shrink-0">
<Skeleton width={40} height={12} variant="text" />
<Skeleton width={65} height={14} variant="text" />
<Skeleton width={40} height={6} variant="text" />
<Skeleton width={65} height={8} variant="text" />
</div>
<div className="leading-relaxed flex flex-col overflow-visible flex-shrink-0">
<Skeleton width={40} height={12} variant="text" />
<Skeleton width={65} height={14} variant="text" />
<Skeleton width={40} height={6} variant="text" />
<Skeleton width={65} height={8} variant="text" />
</div>
<div className="leading-relaxed flex flex-col overflow-visible flex-shrink-0">
<Skeleton width={40} height={12} variant="text" />
<Skeleton width={65} height={14} variant="text" />
<Skeleton width={40} height={6} variant="text" />
<Skeleton width={65} height={8} variant="text" />
</div>
</Stack>
);
+3 -13
View File
@@ -1,5 +1,4 @@
import Collapse from "@mui/material/Collapse";
import Skeleton from "@mui/material/Skeleton";
import {
CopyIcon,
EllipsisIcon,
@@ -37,6 +36,7 @@ import {
} from "#/components/DropdownMenu/DropdownMenu";
import { ExternalImage } from "#/components/ExternalImage/ExternalImage";
import type { Line } from "#/components/Logs/LogLine";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import {
Tabs,
TabsContent,
@@ -426,18 +426,8 @@ export const AgentRow: FC<AgentRowProps> = ({
{agent.status === "connecting" && !isExternalAgent && (
<section className="flex flex-wrap gap-4 [&:empty]:hidden">
<Skeleton
width={80}
height={32}
variant="rectangular"
className="rounded"
/>
<Skeleton
width={110}
height={32}
variant="rectangular"
className="rounded"
/>
<Skeleton width={80} height={32} className="rounded" />
<Skeleton width={110} height={32} className="rounded" />
</section>
)}
@@ -1,13 +1,14 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import { useTheme } from "@emotion/react";
import { InfoIcon } from "lucide-react";
import type { WorkspaceBuild } from "#/api/typesGenerated";
import { BuildIcon } from "#/components/BuildIcon/BuildIcon";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "#/components/Tooltip/Tooltip";
import { cn } from "#/utils/cn";
import { createDayString } from "#/utils/createDayString";
import {
buildReasonLabels,
@@ -21,26 +22,20 @@ export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
const statusType = getDisplayWorkspaceBuildStatus(theme, build).type;
return (
<div css={styles.root}>
<div className="flex flex-row items-center gap-3 leading-normal">
<BuildIcon
transition={build.transition}
className="size-4"
css={{
width: 16,
height: 16,
color: theme.roles[statusType].fill.solid,
}}
/>
<div className="overflow-hidden">
<div className="overflow-hidden flex flex-col">
<div
css={{
color: theme.palette.text.primary,
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
display: "flex",
alignItems: "center",
gap: 4,
}}
className={cn(
"text-content-primary text-ellipsis overflow-hidden",
"whitespace-nowrap flex items-center gap-1",
)}
>
<span className="capitalize">{build.transition}</span> by{" "}
<span className="font-medium">
@@ -63,12 +58,7 @@ export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
</Tooltip>
)}
</div>
<div
css={{
fontSize: 12,
color: theme.palette.text.secondary,
}}
>
<div className="text-xs font-normal text-content-secondary">
{createDayString(build.created_at)}
</div>
</div>
@@ -78,22 +68,12 @@ export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
export const WorkspaceBuildDataSkeleton = () => {
return (
<div css={styles.root}>
<div className="flex flex-row items-center gap-3 leading-normal">
<Skeleton variant="circular" width={16} height={16} />
<div>
<Skeleton variant="text" width={94} height={16} />
<Skeleton variant="text" width={60} height={14} className="mt-0.5" />
<div className="flex flex-col">
<Skeleton variant="text" width={94} height={10} />
<Skeleton variant="text" width={60} height={8} />
</div>
</div>
);
};
const styles = {
root: {
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 12,
lineHeight: "1.5",
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,5 +1,3 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import { saveAs } from "file-saver";
import JSZip from "jszip";
import { type FC, useEffect, useMemo, useRef, useState } from "react";
@@ -13,7 +11,9 @@ import {
ConfirmDialog,
type ConfirmDialogProps,
} from "#/components/Dialogs/ConfirmDialog/ConfirmDialog";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { Stack } from "#/components/Stack/Stack";
import { cn } from "#/utils/cn";
type DownloadLogsDialogProps = Pick<
ConfirmDialogProps,
@@ -34,8 +34,6 @@ export const DownloadLogsDialog: FC<DownloadLogsDialogProps> = ({
onClose,
download = saveAs,
}) => {
const theme = useTheme();
const buildLogsQuery = useQuery({
...buildLogs(workspace),
enabled: open,
@@ -132,7 +130,7 @@ export const DownloadLogsDialog: FC<DownloadLogsDialogProps> = ({
downloadTimeoutIdRef.current = window.setTimeout(() => {
setIsDownloading(false);
}, theme.transitions.duration.leavingScreen);
}, 200);
} catch (error) {
setIsDownloading(false);
toast.error(`Error downloading workspace "${workspace.name}" logs.`, {
@@ -155,7 +153,7 @@ export const DownloadLogsDialog: FC<DownloadLogsDialogProps> = ({
</Alert>
)}
<ul css={styles.list}>
<ul className="list-none p-0 m-0 flex flex-col gap-2">
{allFiles.map((f) => (
<DownloadingItem
key={f.name}
@@ -177,7 +175,6 @@ type DownloadingItemProps = Readonly<{
}>;
const DownloadingItem: FC<DownloadingItemProps> = ({ file, giveUpTimeMs }) => {
const theme = useTheme();
const [isWaiting, setIsWaiting] = useState(true);
useEffect(() => {
@@ -197,24 +194,34 @@ const DownloadingItem: FC<DownloadingItemProps> = ({ file, giveUpTimeMs }) => {
const { baseName, fileExtension } = extractFileNameInfo(file.name);
return (
<li css={styles.listItem}>
<li className="w-full flex justify-between items-center gap-x-8">
<span
css={[
styles.listItemPrimary,
!isWaiting && { color: theme.palette.text.disabled },
]}
className={cn(
"font-medium text-content-primary",
"flex flex-row flex-nowrap gap-x-0 overflow-hidden",
!isWaiting && "text-content-disabled",
)}
>
<span css={styles.listItemPrimaryBaseName}>{baseName}</span>
<span css={styles.listItemPrimaryFileExtension}>.{fileExtension}</span>
<span className="min-w-0 flex-shrink overflow-hidden text-ellipsis">
{baseName}
</span>
<span className="flex-shrink-0">.{fileExtension}</span>
</span>
<span css={styles.listItemSecondary}>
<span className="flex-shrink-0 text-sm whitespace-nowrap">
{file.blob ? (
humanBlobSize(file.blob.size)
) : isWaiting ? (
<Skeleton variant="text" width={48} height={12} />
) : (
<p css={styles.notAvailableText}>Not available</p>
<p
className={cn(
"flex flex-row flex-nowrap items-center gap-x-1",
"text-content-disabled",
)}
>
Not available
</p>
)}
</span>
</li>
@@ -264,56 +271,3 @@ function extractFileNameInfo(filename: string): FileNameInfo {
fileExtension: filename.slice(periodIndex + 1),
};
}
const styles = {
list: {
listStyle: "none",
padding: 0,
margin: 0,
display: "flex",
flexDirection: "column",
gap: 8,
},
listItem: {
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
columnGap: "32px",
},
listItemPrimary: (theme) => ({
fontWeight: 500,
color: theme.palette.text.primary,
display: "flex",
flexFlow: "row nowrap",
columnGap: 0,
overflow: "hidden",
}),
listItemPrimaryBaseName: {
minWidth: 0,
flexShrink: 1,
overflow: "hidden",
textOverflow: "ellipsis",
},
listItemPrimaryFileExtension: {
flexShrink: 0,
},
listItemSecondary: {
flexShrink: 0,
fontSize: 14,
whiteSpace: "nowrap",
},
notAvailableText: (theme) => ({
display: "flex",
flexFlow: "row nowrap",
alignItems: "center",
columnGap: "4px",
color: theme.palette.text.disabled,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,6 +1,5 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { useTheme } from "@emotion/react";
import Link from "@mui/material/Link";
import Skeleton from "@mui/material/Skeleton";
import { InfoIcon, RotateCcwIcon } from "lucide-react";
import { type FC, type ReactNode, useState } from "react";
import { useQuery } from "react-query";
@@ -18,6 +17,7 @@ import {
HelpPopoverTitle,
HelpPopoverTrigger,
} from "#/components/HelpPopover/HelpPopover";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { linkToTemplate, useLinks } from "#/modules/navigation";
import {
useWorkspaceUpdate,
@@ -40,13 +40,22 @@ export const WorkspaceOutdatedTooltip: FC<WorkspaceOutdatedTooltipProps> = ({
{children ? (
<HelpPopoverTrigger asChild>
<span className="flex items-center gap-1.5 cursor-help">
<InfoIcon css={styles.icon} size={14} />
<InfoIcon
css={(theme) => ({
color: theme.roles.notice.outline,
})}
size={14}
/>
<span>{children}</span>
</span>
</HelpPopoverTrigger>
) : (
<HelpPopoverIconTrigger size="small" hoverEffect={false}>
<InfoIcon css={styles.icon} />
<InfoIcon
css={(theme) => ({
color: theme.roles.notice.outline,
})}
/>
<span className="sr-only">Outdated info</span>
</HelpPopoverIconTrigger>
)}
@@ -92,9 +101,11 @@ const WorkspaceOutdatedTooltipContent: FC<TooltipContentProps> = ({
This workspace version is outdated and a newer version is available.
</HelpPopoverText>
<div css={styles.container}>
<div className="flex flex-col gap-2 py-2 text-[13px]">
<div className="leading-[1.6]">
<div css={styles.bold}>New version</div>
<div className="text-content-primary font-semibold">
New version
</div>
<div>
{activeVersion ? (
<Link
@@ -111,7 +122,7 @@ const WorkspaceOutdatedTooltipContent: FC<TooltipContentProps> = ({
</div>
<div className="leading-[1.6]">
<div css={styles.bold}>Message</div>
<div className="text-content-primary font-semibold">Message</div>
<div>
{activeVersion ? (
activeVersion.message || "No message"
@@ -135,23 +146,3 @@ const WorkspaceOutdatedTooltipContent: FC<TooltipContentProps> = ({
</>
);
};
const styles = {
icon: (theme) => ({
color: theme.roles.notice.outline,
}),
container: {
display: "flex",
flexDirection: "column",
gap: 8,
paddingTop: 8,
paddingBottom: 8,
fontSize: 13,
},
bold: (theme) => ({
color: theme.palette.text.primary,
fontWeight: 600,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,5 +1,4 @@
import Collapse from "@mui/material/Collapse";
import Skeleton from "@mui/material/Skeleton";
import sortBy from "lodash/sortBy";
import uniqBy from "lodash/uniqBy";
import { type FC, useState } from "react";
@@ -10,6 +9,7 @@ import type {
} from "#/api/typesGenerated";
import { ChevronDownIcon } from "#/components/AnimatedIcons/ChevronDown";
import { Button } from "#/components/Button/Button";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import {
calcDuration,
formatTime,
@@ -1,6 +1,5 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { useTheme } from "@emotion/react";
import MuiLink from "@mui/material/Link";
import Skeleton from "@mui/material/Skeleton";
import { PlusIcon, RotateCwIcon } from "lucide-react";
import type { FC } from "react";
import Confetti from "react-confetti";
@@ -13,6 +12,7 @@ import {
SettingsHeaderDescription,
SettingsHeaderTitle,
} from "#/components/SettingsHeader/SettingsHeader";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { Spinner } from "#/components/Spinner/Spinner";
import { Stack } from "#/components/Stack/Stack";
import {
@@ -112,9 +112,7 @@ const LicensesSettingsPageView: FC<Props> = ({
</Stack>
<div className="flex flex-col gap-4">
{isLoading && (
<Skeleton className="rounded" variant="rectangular" height={78} />
)}
{isLoading && <Skeleton height={78} />}
{!isLoading && licenses && licenses?.length > 0 && (
<Stack spacing={4} className="licenses">
@@ -139,13 +137,13 @@ const LicensesSettingsPageView: FC<Props> = ({
)}
{!isLoading && licenses?.length === 0 && (
<div css={styles.root}>
<div className="min-h-[240px] flex items-center justify-center rounded-lg border border-solid border-border p-12">
<Stack alignItems="center" spacing={1}>
<Stack alignItems="center" spacing={0.5}>
<span css={styles.title}>
<span className="text-base">
You don&apos;t have any licenses!
</span>
<span css={styles.description}>
<span className="text-content-secondary text-center max-w-[464px] mt-2">
You&apos;re missing out on high availability, RBAC, quotas,
and much more. Contact{" "}
<MuiLink href="mailto:sales@coder.com">sales</MuiLink> or{" "}
@@ -195,27 +193,4 @@ const LicensesSettingsPageView: FC<Props> = ({
);
};
const styles = {
title: {
fontSize: 16,
},
root: (theme) => ({
minHeight: 240,
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 8,
border: `1px solid ${theme.palette.divider}`,
padding: 48,
}),
description: (theme) => ({
color: theme.palette.text.secondary,
textAlign: "center",
maxWidth: 464,
marginTop: 8,
}),
} satisfies Record<string, Interpolation<Theme>>;
export default LicensesSettingsPageView;
+2 -14
View File
@@ -1,5 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import { ChevronRightIcon, PlusIcon } from "lucide-react";
import type { FC } from "react";
import { Link as RouterLink, useNavigate } from "react-router";
@@ -12,6 +10,7 @@ import { Button } from "#/components/Button/Button";
import { ChooseOne, Cond } from "#/components/Conditionals/ChooseOne";
import { EmptyState } from "#/components/EmptyState/EmptyState";
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import {
Table,
TableBody,
@@ -154,7 +153,7 @@ const GroupRow: FC<GroupRowProps> = ({ group }) => {
</TableCell>
<TableCell>
<div css={styles.arrowCell}>
<div className="flex">
<ChevronRightIcon className="size-icon-sm" />
</div>
</TableCell>
@@ -181,14 +180,3 @@ const TableLoader: FC = () => {
</TableLoaderSkeleton>
);
};
const styles = {
arrowRight: (theme) => ({
color: theme.palette.text.secondary,
width: 20,
height: 20,
}),
arrowCell: {
display: "flex",
},
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,10 +1,10 @@
import Skeleton from "@mui/material/Skeleton";
import { BellIcon, BellOffIcon } from "lucide-react";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { toast } from "sonner";
import { healthSettings, updateHealthSettings } from "#/api/queries/debug";
import type { HealthSection } from "#/api/typesGenerated";
import { Button } from "#/components/Button/Button";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { Spinner } from "#/components/Spinner/Spinner";
export const DismissWarningButton = (props: { healthcheck: HealthSection }) => {
@@ -18,14 +18,7 @@ export const DismissWarningButton = (props: { healthcheck: HealthSection }) => {
const dismissMutation = useMutation(updateHealthSettings(queryClient));
if (!healthSettingsQuery.data) {
return (
<Skeleton
variant="rectangular"
height={36}
width={170}
className="rounded-lg"
/>
);
return <Skeleton height={36} width={170} className="rounded-lg" />;
}
const { dismissed_healthchecks } = healthSettingsQuery.data;
@@ -1,5 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import { EllipsisVertical, PlusIcon } from "lucide-react";
import type { FC } from "react";
import { Link as RouterLink, useNavigate } from "react-router";
@@ -14,6 +12,7 @@ import {
} from "#/components/DropdownMenu/DropdownMenu";
import { EmptyState } from "#/components/EmptyState/EmptyState";
import { PaywallPremium } from "#/components/Paywall/PaywallPremium";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { Stack } from "#/components/Stack/Stack";
import {
Table,
@@ -64,8 +63,8 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
justifyContent="space-between"
>
<span>
<h2 css={styles.tableHeader}>Custom Roles</h2>
<span css={styles.tableDescription}>
<h2 className="mb-0 text-lg">Custom Roles</h2>
<span className="text-sm text-content-secondary leading-relaxed">
Create custom roles to grant users a tailored set of granular
permissions.
</span>
@@ -88,8 +87,8 @@ export const CustomRolesPageView: FC<CustomRolesPageViewProps> = ({
onDeleteRole={onDeleteRole}
/>
<span>
<h2 css={styles.tableHeader}>Built-In Roles</h2>
<span css={styles.tableDescription}>
<h2 className="mb-0 text-lg">Built-In Roles</h2>
<span className="text-sm text-content-secondary leading-relaxed">
Built-in roles have predefined permissions. You cannot edit or delete
built-in roles.
</span>
@@ -262,18 +261,3 @@ const TableLoader = () => {
</TableLoaderSkeleton>
);
};
const styles = {
secondary: (theme) => ({
color: theme.palette.text.secondary,
}),
tableHeader: () => ({
marginBottom: 0,
fontSize: 18,
}),
tableDescription: (theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
lineHeight: "160%",
}),
} satisfies Record<string, Interpolation<Theme>>;
+1 -1
View File
@@ -1,4 +1,3 @@
import Skeleton from "@mui/material/Skeleton";
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
import { type FC, useState } from "react";
import { keepPreviousData, useQuery } from "react-query";
@@ -19,6 +18,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "#/components/Popover/Popover";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { useDebouncedValue } from "#/hooks/debounce";
import { useAuthenticated } from "#/hooks/useAuthenticated";
import { cn } from "#/utils/cn";
@@ -1,5 +1,3 @@
import type { Interpolation, Theme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import {
@@ -29,12 +27,14 @@ import {
import { EmptyState } from "#/components/EmptyState/EmptyState";
import { ExternalImage } from "#/components/ExternalImage/ExternalImage";
import { LastSeen } from "#/components/LastSeen/LastSeen";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { TableCell, TableRow } from "#/components/Table/Table";
import {
TableLoaderSkeleton,
TableRowSkeleton,
} from "#/components/TableLoader/TableLoader";
import { AISeatCell } from "#/modules/users/AISeatCell";
import { cn } from "#/utils/cn";
import { UserRoleCell } from "../../OrganizationSettingsPage/UserTable/UserRoleCell";
import { UserGroupsCell } from "./UserGroupsCell";
@@ -187,10 +187,10 @@ export const UsersTableBody: FC<UsersTableBodyProps> = ({
</TableCell>
<TableCell
css={[
styles.status,
user.status === "suspended" && styles.suspended,
]}
className={cn(
"capitalize",
user.status === "suspended" && "text-content-secondary",
)}
>
<div>{user.status}</div>
{(user.status === "active" || user.status === "dormant") && (
@@ -283,27 +283,27 @@ const LoginType: FC<LoginTypeProps> = ({ authMethods, value }) => {
if (value === "password") {
displayName = "Password";
icon = <UserLockIcon css={styles.icon} />;
icon = <UserLockIcon className="size-icon-xs" />;
} else if (value === "none") {
displayName = "None";
icon = <BanIcon css={styles.icon} />;
icon = <BanIcon className="size-icon-xs" />;
} else if (value === "github") {
displayName = "GitHub";
icon = <ExternalImage src="/icon/github.svg" css={styles.icon} />;
icon = <ExternalImage src="/icon/github.svg" className="size-icon-xs" />;
} else if (value === "token") {
displayName = "Token";
icon = <KeyIcon css={styles.icon} />;
icon = <KeyIcon className="size-icon-xs" />;
} else if (value === "oidc") {
displayName =
authMethods.oidc.signInText === "" ? "OIDC" : authMethods.oidc.signInText;
icon =
authMethods.oidc.iconUrl === "" ? (
<ShieldIcon css={styles.icon} />
<ShieldIcon className="size-icon-xs" />
) : (
<img
alt="Open ID Connect icon"
src={authMethods.oidc.iconUrl}
css={styles.icon}
className="size-icon-xs"
/>
);
}
@@ -315,18 +315,3 @@ const LoginType: FC<LoginTypeProps> = ({ authMethods, value }) => {
</div>
);
};
const styles = {
icon: {
width: 14,
height: 14,
},
status: {
textTransform: "capitalize",
},
suspended: (theme) => ({
color: theme.palette.text.secondary,
}),
} satisfies Record<string, Interpolation<Theme>>;
@@ -1,5 +1,3 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import type { FC } from "react";
import type { WorkspaceResource } from "#/api/typesGenerated";
import {
@@ -7,6 +5,7 @@ import {
SidebarCaption,
SidebarItem,
} from "#/components/FullPageLayout/Sidebar";
import { Skeleton } from "#/components/Skeleton/Skeleton";
import { getResourceIconPath } from "#/utils/workspace";
type ResourcesSidebarProps = {
@@ -22,21 +21,11 @@ export const ResourcesSidebar: FC<ResourcesSidebarProps> = ({
isSelected,
resources,
}) => {
const theme = useTheme();
return (
<Sidebar>
<SidebarCaption>Resources</SidebarCaption>
{failed && (
<p
css={{
margin: 0,
padding: "0 16px",
fontSize: 13,
color: theme.palette.text.secondary,
lineHeight: "1.5",
}}
>
<p className="m-0 py-4 text-[13px] text-content-secondary leading-normal">
Your workspace build failed, so the necessary resources couldn&apos;t
be created.
</p>
@@ -53,7 +42,7 @@ export const ResourcesSidebar: FC<ResourcesSidebarProps> = ({
onClick={() => onChange(r)}
isActive={isSelected(r)}
key={r.id}
css={styles.root}
className="leading-normal flex items-center gap-3"
>
<div className="flex items-center justify-center leading-none w-4 h-4 p-0.5">
<img
@@ -64,9 +53,7 @@ export const ResourcesSidebar: FC<ResourcesSidebarProps> = ({
</div>
<div className="flex flex-col font-medium">
<span>{r.name}</span>
<span css={{ fontSize: 12, color: theme.palette.text.secondary }}>
{r.type}
</span>
<span className="text-sm text-content-secondary">{r.type}</span>
</div>
</SidebarItem>
))}
@@ -76,7 +63,7 @@ export const ResourcesSidebar: FC<ResourcesSidebarProps> = ({
const ResourceSidebarItemSkeleton: FC = () => {
return (
<div css={[styles.root, { pointerEvents: "none" }]}>
<div className="leading-normal flex items-center gap-3 pointer-events-none">
<Skeleton variant="circular" width={16} height={16} />
<div>
<Skeleton variant="text" width={94} height={16} />
@@ -85,12 +72,3 @@ const ResourceSidebarItemSkeleton: FC = () => {
</div>
);
};
const styles = {
root: {
lineHeight: "1.5",
display: "flex",
alignItems: "center",
gap: 12,
},
} satisfies Record<string, Interpolation<Theme>>;