fix: resolve manual button icon sizes (#22058)

Closes #21830 

Remove redundant icon sizing across the frontend. Components like
`Button`, `DropdownMenuItem`, and `CommandItem` already control child
SVG sizes via CSS selectors (e.g., `[&>svg]:size-icon-lg`), so explicit
`size` props and `className` overrides on icons nested inside them are
unnecessary. This PR strips those out and lets parent components handle
sizing consistently.

As a bonus, also migrates the `DropdownArrow` component from Emotion
CSS-in-JS to Tailwind utilities, replaces raw `<a>` tags with the `<Link
/>` component in the Premium page, and adds Storybook coverage for
`PremiumPageView`.
This commit is contained in:
Jake Howell
2026-02-13 05:25:04 +11:00
committed by GitHub
parent 1e1d312cab
commit 21d4d0196d
31 changed files with 81 additions and 109 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ export const Alert: FC<AlertProps> = ({
data-testid="dismiss-banner-btn"
aria-label="Dismiss"
>
<XIcon className="!size-icon-sm !p-0" />
<XIcon className="!p-0" />
</Button>
)}
</div>
@@ -189,7 +189,7 @@ export function Autocomplete<TOption>({
<span className="flex items-center justify-center size-5">
<ChevronDown
className={cn(
"size-4 text-content-secondary transition-transform",
"size-icon-lg text-content-secondary transition-transform p-0.5",
isOpen && "rotate-180",
)}
/>
@@ -20,7 +20,7 @@ const meta: Meta<typeof Collapsible> = {
</h4>
<CollapsibleTrigger asChild>
<Button size="sm">
<ChevronsUpDown className="h-4 w-4" />
<ChevronsUpDown />
<span className="sr-only">Toggle</span>
</Button>
</CollapsibleTrigger>
+1 -1
View File
@@ -86,7 +86,7 @@ export const Combobox: FC<ComboboxProps> = ({
<span className={cn(!value && "text-content-secondary")}>
{optionsMap.get(value)?.displayName || value || placeholder}
</span>
<ChevronDown className="size-icon-sm text-content-secondary group-hover:text-content-primary" />
<ChevronDown className="text-content-secondary group-hover:text-content-primary" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[var(--radix-popover-trigger-width)]">
@@ -14,4 +14,3 @@ type Story = StoryObj<typeof DropdownArrow>;
export const Open: Story = {};
export const Close: Story = { args: { close: true } };
export const WithColor: Story = { args: { color: "#f00" } };
@@ -1,37 +1,19 @@
import type { Interpolation, Theme } from "@emotion/react";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import type { FC } from "react";
import { cn } from "utils/cn";
interface ArrowProps {
margin?: boolean;
color?: string;
close?: boolean;
}
export const DropdownArrow: FC<ArrowProps> = ({
margin = true,
color,
close,
}) => {
export const DropdownArrow: FC<ArrowProps> = ({ margin = true, close }) => {
const Arrow = close ? ChevronUpIcon : ChevronDownIcon;
return (
<Arrow
aria-label={close ? "close-dropdown" : "open-dropdown"}
css={[styles.base, margin && styles.withMargin]}
style={{ color }}
className={cn("text-current", margin && "ml-2")}
/>
);
};
const styles = {
base: {
color: "currentcolor",
width: 16,
height: 16,
},
withMargin: {
marginLeft: 8,
},
} satisfies Record<string, Interpolation<Theme>>;
+1 -1
View File
@@ -288,7 +288,7 @@ const PresetMenu: FC<PresetMenuProps> = ({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<SlidersHorizontal className="size-icon-xs" />
<SlidersHorizontal />
Filters
</Button>
</DropdownMenuTrigger>
@@ -486,7 +486,7 @@ export const MultiSelectCombobox = forwardRef<
<div
className={cn(
`min-h-10 rounded-md border border-solid border-border text-sm pr-3
focus-within:ring-2 focus-within:ring-content-link`,
focus-within:ring-2 focus-within:ring-content-link [&>svg]:p-0.5`,
{
"pl-3 py-1": selected.length !== 0,
"cursor-text": !disabled && selected.length !== 0,
@@ -598,7 +598,9 @@ export const MultiSelectCombobox = forwardRef<
}
}}
className={cn(
"bg-transparent mt-1 border-none rounded-sm cursor-pointer text-content-secondary hover:text-content-primary outline-none focus:ring-2 focus:ring-content-link",
"bg-transparent mt-1 border-none rounded-sm",
"cursor-pointer text-content-secondary hover:text-content-primary",
"outline-none focus:ring-2 focus:ring-content-link [&>svg]:p-0.5",
(hideClearAllButton ||
disabled ||
selected.length < 1 ||
@@ -608,7 +610,7 @@ export const MultiSelectCombobox = forwardRef<
>
<X className="h-5 w-5" />
</button>
<ChevronDown className="size-icon-sm cursor-pointer text-content-secondary hover:text-content-primary" />
<ChevronDown className="cursor-pointer text-content-secondary hover:text-content-primary p-0.5" />
</div>
</div>
</div>
@@ -23,7 +23,7 @@ export const PageHeader: FC<PageHeaderProps> = ({
>
<hgroup className="flex flex-col gap-2">{children}</hgroup>
{actions && (
<div className="flex ml-[initial] md:ml-auto w-full md:w-auto">
<div className="flex items-center gap-2 ml-[initial] md:ml-auto w-full md:w-auto">
{actions}
</div>
)}
+1 -1
View File
@@ -34,7 +34,7 @@ export const SelectTrigger: React.FC<SelectTriggerProps> = ({
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="size-icon-sm cursor-pointer text-content-secondary hover:text-content-primary" />
<ChevronDown className="cursor-pointer text-content-secondary hover:text-content-primary p-0.5" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
@@ -43,7 +43,7 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
<DropdownMenuTrigger asChild>
<Button variant="outline" size="lg">
Admin settings
<ChevronDownIcon className="text-content-primary !size-icon-sm" />
<ChevronDownIcon className="text-content-primary" />
</Button>
</DropdownMenuTrigger>
@@ -271,9 +271,7 @@ const SupportButton: FC<SupportButtonProps> = ({ name, target, icon }) => {
rel="noreferrer"
className="inline-block"
>
{icon && (
<SupportIcon icon={icon} className="size-5 text-content-secondary" />
)}
{icon && <SupportIcon icon={icon} className="text-content-secondary" />}
{name}
<span className="sr-only"> (link opens in new tab)</span>
</a>
@@ -95,7 +95,7 @@ export const ProxyMenu: FC<ProxyMenuProps> = ({ proxyContextValue }) => {
"Select Proxy"
)}
<ChevronDownIcon className="text-content-primary !size-icon-sm" />
<ChevronDownIcon className="text-content-primary" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-80">
@@ -78,7 +78,7 @@ export const OrganizationSidebarView: FC<
) : (
<span className="truncate">No organization selected</span>
)}
<ChevronDown className="ml-auto !size-icon-sm" />
<ChevronDown className="ml-auto" />
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-60">
@@ -109,7 +109,7 @@ export const OrganizationSidebarView: FC<
{organization?.display_name || organization?.name}
</span>
{activeOrganization?.name === organization.name && (
<Check size={16} strokeWidth={2} className="ml-auto" />
<Check className="ml-auto" />
)}
</CommandItem>
))}
@@ -98,7 +98,7 @@ export const PortForwardButton: FC<PortForwardButtonProps> = ({
<span css={styles.portCount}>{listeningPorts?.length}</span>
</Spinner>
Open ports
<ChevronDownIcon className="size-4" />
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent
@@ -534,13 +534,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
});
}}
>
<XIcon
css={{
width: 14,
height: 14,
color: theme.palette.text.primary,
}}
/>
<XIcon />
</Button>
</Stack>
</Stack>
@@ -29,7 +29,7 @@ export const PromptSelectTrigger: FC<PromptSelectTriggerProps> = ({
{...props}
className={cn([
`w-full md:w-auto max-w-full overflow-hidden border-0 bg-surface-secondary text-sm text-content-primary gap-2 px-4 md:px-3
[&_svg]:text-inherit cursor-pointer hover:bg-surface-quaternary rounded-full
[&_svg]:text-inherit [&>svg]:p-0.5 cursor-pointer hover:bg-surface-quaternary rounded-full
h-10 md:h-8 data-[state=open]:bg-surface-tertiary`,
className,
])}
@@ -74,7 +74,7 @@ export const UserCombobox: FC<UserComboboxProps> = ({
"Loading users..."
)}
<ChevronsUpDownIcon className="h-4 w-4 shrink-0 opacity-50" />
<ChevronsUpDownIcon className="shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
@@ -107,11 +107,7 @@ export const WorkspaceTimings: FC<WorkspaceTimingsProps> = ({
css={styles.collapseTrigger}
onClick={() => setIsOpen((o) => !o)}
>
{isOpen ? (
<ChevronUpIcon css={{ width: 16, height: 16, marginRight: 16 }} />
) : (
<ChevronDownIcon css={{ width: 16, height: 16, marginRight: 16 }} />
)}
{isOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
<span>Build timeline</span>
<span
css={(theme) => ({
@@ -143,11 +143,7 @@ export const RequestLogsRow: FC<RequestLogsRowProps> = ({ interception }) => {
isOpen && "text-content-primary",
])}
>
{isOpen ? (
<ChevronDownIcon className="size-icon-xs" />
) : (
<ChevronRightIcon className="size-icon-xs" />
)}
{isOpen ? <ChevronDownIcon /> : <ChevronRightIcon />}
<span className="sr-only">({isOpen ? "Hide" : "Show more"})</span>
{formatDate(new Date(interception.started_at))}
</div>
@@ -40,7 +40,7 @@ export const ExportPolicyButton: FC<ExportPolicyButtonProps> = ({
}
}}
>
<Download size={14} />
<Download />
Export Policy
</Button>
);
@@ -290,7 +290,7 @@ export const IdpOrgSyncPageView: FC<IdpSyncPageViewProps> = ({
}}
>
<Spinner loading={form.isSubmitting}>
<Plus size={14} />
<Plus />
</Spinner>
Add IdP organization
</Button>
@@ -95,7 +95,7 @@ const LicensesSettingsPageView: FC<Props> = ({
variant="outline"
>
<Spinner loading={isRefreshing}>
<RotateCwIcon className="size-icon-xs" />
<RotateCwIcon />
</Spinner>
Refresh
</Button>
@@ -0,0 +1,23 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { PremiumPageView } from "./PremiumPageView";
const meta: Meta<typeof PremiumPageView> = {
title: "pages/DeploymentSettingsPage/PremiumPageView",
component: PremiumPageView,
};
export default meta;
type Story = StoryObj<typeof PremiumPageView>;
export const Enterprise: Story = {
args: {
isEnterprise: true,
},
};
export const OSS: Story = {
args: {
isEnterprise: false,
},
};
@@ -1,4 +1,5 @@
import { Button } from "components/Button/Button";
import { Link } from "components/Link/Link";
import { Activity, Coins, Expand, SquareArrowOutUpRight } from "lucide-react";
import type { FC } from "react";
import { docs } from "utils/docs";
@@ -23,24 +24,18 @@ const EnterpriseVersion: FC = () => {
</div>
<Button asChild>
<a href="https://coder.com/contact/sales" className="no-underline">
<SquareArrowOutUpRight size={14} />
<SquareArrowOutUpRight />
Contact sales
</a>
</Button>
</header>
<section className="pb-1">
<a
className="no-underline text-sm text-content-link"
href={docs("/admin/users/organizations")}
>
<span className="flex items-center">
<h2 className="text-sm font-semibold m-0">
Multi-Organization access controls&nbsp;
</h2>
<SquareArrowOutUpRight size={14} />
</span>
</a>
<h2 className="text-sm font-semibold m-0">
<Link className="px-0" href={docs("/admin/users/organizations")}>
Multi-Organization access controls
</Link>
</h2>
<p className="text-sm max-w-xl text-content-secondary mt-0 font-medium">
Manage multiple teams and projects within a single deployment, each
with isolated access.
@@ -48,15 +43,11 @@ const EnterpriseVersion: FC = () => {
</section>
<section className="pb-1">
<a
className="no-underline text-sm text-content-link"
href={docs("/admin/users/groups-roles")}
>
<span className="flex items-center">
<h2 className="text-sm font-semibold m-0">Custom role&nbsp;</h2>
<SquareArrowOutUpRight size={14} />
</span>
</a>
<h2 className="text-sm font-semibold m-0">
<Link className="px-0" href={docs("/admin/users/groups-roles")}>
Custom role
</Link>
</h2>
<p className="text-sm max-w-xl text-content-secondary mt-0 font-medium">
Configure specific permissions for teams or contractors with tailored
roles.
@@ -64,17 +55,11 @@ const EnterpriseVersion: FC = () => {
</section>
<section>
<a
className="no-underline text-sm text-content-link"
href={docs("/admin/users/quotas")}
>
<span className="flex items-center text-sm">
<h2 className="text-sm font-semibold m-0">
Org-Level quotas for chargeback&nbsp;
</h2>
<SquareArrowOutUpRight size={14} />
</span>
</a>
<h2 className="text-sm font-semibold m-0">
<Link className="px-0" href={docs("/admin/users/quotas")}>
Org-Level quotas for chargeback
</Link>
</h2>
<p className="text-sm max-w-xl text-content-secondary mt-0 font-medium">
Set and monitor resource quotas at the organization level to support
internal cost tracking.
@@ -108,7 +93,7 @@ const OSSVersion: FC = () => {
</div>
<Button asChild>
<a href="https://coder.com/contact/sales" className="no-underline">
<SquareArrowOutUpRight size={14} />
<SquareArrowOutUpRight />
Contact sales
</a>
</Button>
@@ -117,7 +102,7 @@ const OSSVersion: FC = () => {
<section className="pb-10 max-w-xl text-sm text-content-secondary">
<h2 className="text-xl text-content-primary m-0">
<span className="flex flex-row items-center">
<Expand size={18} className="text-content-secondary" />
<Expand className="size-icon-sm text-content-secondary" />
&nbsp; Deploy coder at scale
</span>
</h2>
@@ -162,7 +147,7 @@ const OSSVersion: FC = () => {
<section className="pb-10 max-w-xl text-sm text-content-secondary">
<h2 className="text-xl text-content-primary m-0">
<span className="flex flex-row items-center">
<Coins size={18} className="text-content-secondary" />
<Coins className="size-icon-sm text-content-secondary" />
&nbsp; Control infrastructure costs
</span>
</h2>
@@ -207,7 +192,7 @@ const OSSVersion: FC = () => {
<section className="pb-5 max-w-xl text-sm text-content-secondary">
<h2 className="text-xl text-content-primary m-0">
<span className="flex flex-row items-center">
<Activity size={18} className="text-content-secondary" />
<Activity className="size-icon-sm text-content-secondary" />
&nbsp; Govern workspace activity
</span>
</h2>
@@ -295,7 +295,7 @@ export const IdpGroupSyncForm: FC<IdpGroupSyncFormProps> = ({
}}
>
<Spinner loading={form.isSubmitting}>
<Plus size={14} />
<Plus />
</Spinner>
Add IdP group
</Button>
@@ -235,7 +235,7 @@ export const IdpRoleSyncForm: FC<IdpRoleSyncFormProps> = ({
}}
>
<Spinner loading={form.isSubmitting}>
<Plus size={14} />
<Plus />
</Spinner>
Add IdP role
</Button>
+1 -1
View File
@@ -239,7 +239,7 @@ const TasksPage: FC = () => {
>
Bulk actions
<Spinner loading={batchActions.isProcessing}>
<ChevronDownIcon className="size-4" />
<ChevronDownIcon />
</Spinner>
</Button>
</DropdownMenuTrigger>
@@ -31,7 +31,7 @@ export const IntervalMenu: FC<IntervalMenuProps> = ({ value, onChange }) => {
<DropdownMenuTrigger asChild>
<Button variant="outline">
{insightsIntervals[value].label}
<ChevronDownIcon className="!size-icon-xs" />
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
@@ -32,7 +32,7 @@ export const WeekPicker: FC<WeekPickerProps> = ({ value, onChange }) => {
<DropdownMenuTrigger asChild>
<Button variant="outline">
Last {numberOfWeeks} weeks
<ChevronDownIcon className="!size-icon-xs" />
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
@@ -27,11 +27,8 @@ export const ProvisionerTagsPopover: FC<ProvisionerTagsPopoverProps> = ({
return (
<Popover>
<PopoverTrigger asChild>
<TopbarButton
color="neutral"
css={{ paddingLeft: 0, paddingRight: 0, minWidth: "28px !important" }}
>
<ChevronDownIcon className="size-icon-xs" />
<TopbarButton color="neutral" size="icon">
<ChevronDownIcon />
<span className="sr-only">Expand provisioner tags</span>
</TopbarButton>
</PopoverTrigger>
@@ -150,7 +150,7 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({
>
Bulk actions
<Spinner loading={isRunningBatchAction}>
<ChevronDownIcon className="size-4" />
<ChevronDownIcon />
</Spinner>
</Button>
</DropdownMenuTrigger>