mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: convert emotion styles to tailwind (#19347)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { FC, HTMLAttributes, ReactNode } from "react";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
export interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/** Text Message to display, placed inside Typography component */
|
||||
@@ -21,44 +22,25 @@ export const EmptyState: FC<EmptyStateProps> = ({
|
||||
cta,
|
||||
image,
|
||||
isCompact,
|
||||
className,
|
||||
...attrs
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
css={[
|
||||
{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
minHeight: 360,
|
||||
padding: "80px 40px",
|
||||
position: "relative",
|
||||
},
|
||||
isCompact && {
|
||||
minHeight: 180,
|
||||
padding: "10px 40px",
|
||||
},
|
||||
]}
|
||||
className={cn(
|
||||
"overflow-hidden flex flex-col justify-center items-center text-center min-h-96 py-20 px-10 relative",
|
||||
isCompact && "min-h-44 py-2.5",
|
||||
className,
|
||||
)}
|
||||
{...attrs}
|
||||
>
|
||||
<h5 css={{ fontSize: 24, fontWeight: 500, margin: 0 }}>{message}</h5>
|
||||
<h5 className="text-2xl font-medium m-0">{message}</h5>
|
||||
{description && (
|
||||
<p
|
||||
css={(theme) => ({
|
||||
marginTop: 16,
|
||||
fontSize: 16,
|
||||
lineHeight: "140%",
|
||||
maxWidth: 480,
|
||||
color: theme.palette.text.secondary,
|
||||
})}
|
||||
>
|
||||
<p className="mt-4 line-height-[140%] max-w-md text-content-secondary">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
{cta && <div css={{ marginTop: 24 }}>{cta}</div>}
|
||||
{cta && <div className="mt-6">{cta}</div>}
|
||||
{image}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ export const FullPageForm: FC<FullPageFormProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<Margins size="small">
|
||||
<PageHeader css={{ paddingBottom: 24 }}>
|
||||
<PageHeader className="pb-6">
|
||||
<PageHeaderTitle>{title}</PageHeaderTitle>
|
||||
{detail && <PageHeaderSubtitle>{detail}</PageHeaderSubtitle>}
|
||||
</PageHeader>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const LastSeen: FC<LastSeenProps> = ({ at, className, ...attrs }) => {
|
||||
return (
|
||||
<span
|
||||
data-chromatic="ignore"
|
||||
css={{ color }}
|
||||
style={{ color }}
|
||||
{...attrs}
|
||||
className={cn(["whitespace-nowrap", className])}
|
||||
>
|
||||
|
||||
@@ -25,11 +25,7 @@ export const Latency: FC<LatencyProps> = ({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Tooltip title="Loading latency...">
|
||||
<CircularProgress
|
||||
size={size}
|
||||
css={{ marginLeft: "auto" }}
|
||||
style={{ color }}
|
||||
/>
|
||||
<CircularProgress size={size} className="ml-auto" style={{ color }} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -38,22 +34,20 @@ export const Latency: FC<LatencyProps> = ({
|
||||
const notAvailableText = "Latency not available";
|
||||
return (
|
||||
<Tooltip title={notAvailableText}>
|
||||
<CircleHelpIcon
|
||||
className="size-icon-sm"
|
||||
css={{
|
||||
marginLeft: "auto",
|
||||
}}
|
||||
style={{ color }}
|
||||
/>
|
||||
<>
|
||||
<span css={{ ...visuallyHidden }}>{notAvailableText}</span>
|
||||
|
||||
<CircleHelpIcon className="ml-auto size-icon-sm" style={{ color }} />
|
||||
</>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p css={{ fontSize: 13, margin: "0 0 0 auto" }} style={{ color }}>
|
||||
<div className="ml-auto text-sm" style={{ color }}>
|
||||
<span css={{ ...visuallyHidden }}>Latency: </span>
|
||||
{latency.toFixed(0)}
|
||||
<Abbr title="milliseconds">ms</Abbr>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -39,16 +39,7 @@ export const PaginationWidgetBase: FC<PaginationWidgetBaseProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: "0 20px",
|
||||
columnGap: "6px",
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row items-center justify-center px-5 gap-x-1.5">
|
||||
<PaginationNavButton
|
||||
disabledMessage="You are already on the first page"
|
||||
disabled={isPrevDisabled}
|
||||
|
||||
@@ -20,24 +20,26 @@ export const Paywall: FC<PaywallProps> = ({
|
||||
return (
|
||||
<div css={styles.root}>
|
||||
<div>
|
||||
<Stack direction="row" alignItems="center" css={{ marginBottom: 24 }}>
|
||||
<h5 css={styles.title}>{message}</h5>
|
||||
<Stack direction="row" alignItems="center" className="mb-6">
|
||||
<h5 className="font-semibold font-inherit text-xl m-0">{message}</h5>
|
||||
<PremiumBadge />
|
||||
</Stack>
|
||||
|
||||
{description && <p css={styles.description}>{description}</p>}
|
||||
{description && (
|
||||
<p className="font-inherit max-w-md text-sm">{description}</p>
|
||||
)}
|
||||
<Link
|
||||
href={documentationLink}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
css={{ fontWeight: 600 }}
|
||||
className="font-semibold"
|
||||
>
|
||||
Read the documentation
|
||||
</Link>
|
||||
</div>
|
||||
<div css={styles.separator} />
|
||||
<div className="w-px h-[220px] bg-highlight-purple/50 ml-2" />
|
||||
<Stack direction="column" alignItems="left" spacing={3}>
|
||||
<ul css={styles.featureList}>
|
||||
<ul className="m-0 px-6 text-sm font-medium">
|
||||
<li css={styles.feature}>
|
||||
<FeatureIcon />
|
||||
High availability & workspace proxies
|
||||
@@ -55,7 +57,7 @@ export const Paywall: FC<PaywallProps> = ({
|
||||
Unlimited Git & external auth integrations
|
||||
</li>
|
||||
</ul>
|
||||
<div css={styles.learnButton}>
|
||||
<div className="px-7">
|
||||
<Button asChild>
|
||||
<a
|
||||
href="https://coder.com/pricing#compare-plans"
|
||||
@@ -98,36 +100,6 @@ const styles = {
|
||||
backgroundImage: `linear-gradient(160deg, transparent, ${theme.branding.premium.background})`,
|
||||
border: `1px solid ${theme.branding.premium.border}`,
|
||||
}),
|
||||
title: {
|
||||
fontWeight: 600,
|
||||
fontFamily: "inherit",
|
||||
fontSize: 22,
|
||||
margin: 0,
|
||||
},
|
||||
description: () => ({
|
||||
fontFamily: "inherit",
|
||||
maxWidth: 460,
|
||||
fontSize: 14,
|
||||
}),
|
||||
separator: (theme) => ({
|
||||
width: 1,
|
||||
height: 220,
|
||||
backgroundColor: theme.branding.premium.divider,
|
||||
marginLeft: 8,
|
||||
}),
|
||||
learnButton: {
|
||||
padding: "0 28px",
|
||||
},
|
||||
featureList: {
|
||||
listStyle: "none",
|
||||
margin: 0,
|
||||
padding: "0 24px",
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
},
|
||||
featureIcon: (theme) => ({
|
||||
color: theme.roles.active.fill.outline,
|
||||
}),
|
||||
feature: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -76,7 +76,7 @@ export const LongButtonText: Story = {
|
||||
<SelectMenu>
|
||||
<SelectMenuTrigger>
|
||||
<SelectMenuButton
|
||||
css={{ width: 200 }}
|
||||
className="w-48"
|
||||
startIcon={<Avatar size="sm" fallback={selectedOpt} />}
|
||||
>
|
||||
{selectedOpt}
|
||||
@@ -107,7 +107,7 @@ export const NoSelectedOption: Story = {
|
||||
return (
|
||||
<SelectMenu>
|
||||
<SelectMenuTrigger>
|
||||
<SelectMenuButton css={{ width: 200 }}>All users</SelectMenuButton>
|
||||
<SelectMenuButton className="w-48">All users</SelectMenuButton>
|
||||
</SelectMenuTrigger>
|
||||
<SelectMenuContent>
|
||||
<SelectMenuSearch onChange={action("search")} />
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type ReactElement,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
const SIDE_PADDING = 16;
|
||||
|
||||
@@ -76,19 +77,22 @@ export const SelectMenuSearch: FC<SearchFieldProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectMenuList: FC<MenuListProps> = (props) => {
|
||||
export const SelectMenuList: FC<MenuListProps> = ({
|
||||
children,
|
||||
className,
|
||||
...attrs
|
||||
}) => {
|
||||
const items = useMemo(() => {
|
||||
let children = Children.toArray(props.children);
|
||||
if (!children.every(isValidElement)) {
|
||||
let items = Children.toArray(children);
|
||||
if (!items.every(isValidElement)) {
|
||||
throw new Error("SelectMenuList only accepts MenuItem children");
|
||||
}
|
||||
children = moveSelectedElementToFirst(
|
||||
children as ReactElement<MenuItemProps>[],
|
||||
);
|
||||
return children;
|
||||
}, [props.children]);
|
||||
items = moveSelectedElementToFirst(items as ReactElement<MenuItemProps>[]);
|
||||
return items;
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<MenuList css={{ maxHeight: 480 }} {...props}>
|
||||
<MenuList className={cn("max-h-[480px]", className)} {...attrs}>
|
||||
{items}
|
||||
</MenuList>
|
||||
);
|
||||
@@ -106,25 +110,31 @@ function moveSelectedElementToFirst(items: ReactElement<MenuItemProps>[]) {
|
||||
return newItems;
|
||||
}
|
||||
|
||||
export const SelectMenuIcon: FC<HTMLProps<HTMLDivElement>> = (props) => {
|
||||
return <div css={{ marginRight: 16 }} {...props} />;
|
||||
export const SelectMenuIcon: FC<HTMLProps<HTMLDivElement>> = ({
|
||||
children,
|
||||
className,
|
||||
...attrs
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn("mr-4", className)} {...attrs}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectMenuItem: FC<MenuItemProps> = (props) => {
|
||||
export const SelectMenuItem: FC<MenuItemProps> = ({
|
||||
children,
|
||||
className,
|
||||
selected,
|
||||
...attrs
|
||||
}) => {
|
||||
return (
|
||||
<MenuItem
|
||||
css={{
|
||||
fontSize: 14,
|
||||
gap: 0,
|
||||
lineHeight: 1,
|
||||
padding: `12px ${SIDE_PADDING}px`,
|
||||
}}
|
||||
{...props}
|
||||
className={cn("text-sm gap-0 leading-none py-3 px-4", className)}
|
||||
{...attrs}
|
||||
>
|
||||
{props.children}
|
||||
{props.selected && (
|
||||
<CheckIcon className="size-icon-xs" css={{ marginLeft: "auto" }} />
|
||||
)}
|
||||
{children}
|
||||
{selected && <CheckIcon className="size-icon-xs ml-auto" />}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -106,7 +106,7 @@ export const SidebarNavItem: FC<SidebarNavItemProps> = ({
|
||||
}
|
||||
>
|
||||
<Stack alignItems="center" spacing={1.5} direction="row">
|
||||
<Icon css={{ width: 16, height: 16 }} />
|
||||
<Icon className="size-4" />
|
||||
{children}
|
||||
</Stack>
|
||||
</NavLink>
|
||||
|
||||
@@ -44,9 +44,8 @@ export const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({
|
||||
return (
|
||||
<div
|
||||
data-chromatic="ignore"
|
||||
css={{
|
||||
padding: "8px 0",
|
||||
height: "100%",
|
||||
className="py-2 h-full"
|
||||
style={{
|
||||
backgroundColor: theme.monaco.colors["editor.background"],
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -43,13 +43,7 @@ export const WithImageAndCta: Story = {
|
||||
<img
|
||||
src="/featured/templates.webp"
|
||||
alt=""
|
||||
css={{
|
||||
maxWidth: 800,
|
||||
height: 320,
|
||||
overflow: "hidden",
|
||||
objectFit: "cover",
|
||||
objectPosition: "top",
|
||||
}}
|
||||
className="max-w-3xl h-[320px] overflow-hidden object-cover object-top"
|
||||
/>
|
||||
),
|
||||
style: { paddingBottom: 0 },
|
||||
|
||||
@@ -11,7 +11,7 @@ type TableEmptyProps = EmptyStateProps;
|
||||
export const TableEmpty: FC<TableEmptyProps> = (props) => {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999} css={{ padding: "0 !important" }}>
|
||||
<TableCell colSpan={999} className="p-0!">
|
||||
<EmptyState {...props} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -3,20 +3,7 @@ import type { FC, PropsWithChildren } from "react";
|
||||
|
||||
export const TableToolbar: FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<div
|
||||
css={(theme) => ({
|
||||
fontSize: 13,
|
||||
marginBottom: "8px",
|
||||
marginTop: 0,
|
||||
height: "36px", // The size of a small button
|
||||
color: theme.palette.text.secondary,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& strong": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div className="text-sm mb-2 mt-0 h-9 text-content-secondary flex items-center [&_strong]:text-content-primary">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -42,7 +29,7 @@ export const PaginationStatus: FC<PaginationStatusProps> = (props) => {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div css={{ height: 24, display: "flex", alignItems: "center" }}>
|
||||
<div className="h-6 flex items-center">
|
||||
<Skeleton variant="text" width={160} height={16} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -167,7 +167,7 @@ export const ProxyMenu: FC<ProxyMenuProps> = ({ proxyContextValue }) => {
|
||||
<MenuItem
|
||||
key={proxy.id}
|
||||
selected={proxy.id === selectedProxy?.id}
|
||||
css={{ fontSize: 14 }}
|
||||
className="text-sm"
|
||||
onClick={() => {
|
||||
if (!proxy.healthy) {
|
||||
displayError("Please select a healthy workspace proxy.");
|
||||
@@ -179,23 +179,12 @@ export const ProxyMenu: FC<ProxyMenuProps> = ({ proxyContextValue }) => {
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
gap: 24,
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<div css={{ width: 14, height: 14, lineHeight: 0 }}>
|
||||
<div className="flex gap-6 items-center w-full">
|
||||
<div className="leading-[0] size-[14px]">
|
||||
<img
|
||||
src={proxy.icon_url}
|
||||
alt=""
|
||||
css={{
|
||||
objectFit: "contain",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
className="object-fit size-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
+2
-2
@@ -125,7 +125,7 @@ export const AnnouncementBannerSettings: FC<
|
||||
{!isEntitled || banners.length < 1 ? (
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
css={{ minHeight: 160 }}
|
||||
className="min-h-[160px]"
|
||||
message="No announcement banners"
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -161,7 +161,7 @@ export const AnnouncementBannerSettings: FC<
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div css={{ color: theme.palette.text.secondary }}>
|
||||
<div className="text-content-secondary">
|
||||
<p>
|
||||
Your license does not include Service Banners.{" "}
|
||||
<Link href="mailto:sales@coder.com">Contact sales</Link> to
|
||||
|
||||
@@ -72,7 +72,7 @@ export const EmptyTemplates: FC<EmptyTemplatesProps> = ({
|
||||
}
|
||||
cta={
|
||||
<Stack alignItems="center" spacing={4}>
|
||||
<div css={styles.featuredExamples}>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{featuredExamples.map((example) => (
|
||||
<TemplateExampleCard example={example} key={example.id} />
|
||||
))}
|
||||
@@ -91,7 +91,7 @@ export const EmptyTemplates: FC<EmptyTemplatesProps> = ({
|
||||
|
||||
return (
|
||||
<TableEmpty
|
||||
css={styles.withImage}
|
||||
className="pb-0"
|
||||
message="Create a Template"
|
||||
description="Contact your Coder administrator to create a template. You can share the code below."
|
||||
cta={<CodeExample secret={false} code="coder templates init" />}
|
||||
@@ -105,10 +105,6 @@ export const EmptyTemplates: FC<EmptyTemplatesProps> = ({
|
||||
};
|
||||
|
||||
const styles = {
|
||||
withImage: {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
|
||||
emptyImage: {
|
||||
maxWidth: "50%",
|
||||
height: 320,
|
||||
@@ -119,11 +115,4 @@ const styles = {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
},
|
||||
|
||||
featuredExamples: {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
gap: 16,
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -103,12 +103,7 @@ export const useSingleSignOnSection = () => {
|
||||
const SSOEmptyState: FC = () => {
|
||||
return (
|
||||
<EmptyState
|
||||
css={(theme) => ({
|
||||
minHeight: 0,
|
||||
padding: "48px 32px",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: 8,
|
||||
})}
|
||||
className="rounded-lg border border-solid border-border min-h-0"
|
||||
message="No SSO Providers"
|
||||
description="No SSO providers are configured with this Coder deployment."
|
||||
cta={
|
||||
|
||||
Reference in New Issue
Block a user