mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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>
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+1
-1
@@ -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
|
||||
</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 </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
|
||||
</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" />
|
||||
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" />
|
||||
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" />
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user