authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-21 22:57:43-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-21 22:57:43-07:00
logb5a0f810108d42ca8635b5a3d75a6e885bb3a254
tree0e6da149664827942d11cebd78a9c6abbfffdf0a
parent438bf466fd7e97bb80f0d1f936b2c857830249b7
signature Signed by PGP key B5690EEEBB952194

refactor(web): give Spinner and RefreshIcon a size prop (#12985)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

36 files changed, 101 insertions(+), 59 deletions(-)

apps/web/src/components/BranchToolbarBranchSelector.tsx+1-1
......@@ -905,7 +905,7 @@ export function BranchToolbarBranchSelector({
905905 className="flex cursor-pointer items-center justify-between gap-3 border-t border-border/60 px-3 py-2 text-xs"
906906 >
907907 <span className="flex min-w-0 items-center gap-1.5 font-medium text-muted-foreground">
908 <RefreshIcon aria-hidden="true" className="size-3 shrink-0 opacity-70" />
908 <RefreshIcon aria-hidden="true" size="xs" className="shrink-0" />
909909 <span className="truncate">Start from origin</span>
910910 </span>
911911 <Switch
apps/web/src/components/DiffPanel.tsx+1-1
......@@ -856,7 +856,7 @@ export default function DiffPanel({
856856 />
857857 }
858858 >
859 <RefreshIcon className="size-3.5" refreshing={isRefreshingDiff} />
859 <RefreshIcon size="sm" refreshing={isRefreshingDiff} />
860860 </TooltipTrigger>
861861 <TooltipPopup side="top">
862862 {isRefreshingDiff ? "Refreshing diff…" : "Refresh diff"}
apps/web/src/components/GitActionsControl.tsx+2-2
......@@ -851,7 +851,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
851851 aria-live="polite"
852852 className="flex items-center gap-2 rounded-md border border-input bg-muted/40 px-3 py-2 text-xs text-muted-foreground dark:border-transparent dark:bg-white/[0.035]"
853853 >
854 <Spinner className="size-3.5" aria-hidden />
854 <Spinner size="sm" aria-hidden />
855855 Publishing repository to {publishProviderLabel}...
856856 </div>
857857 ) : null}
......@@ -938,7 +938,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) {
938938 <Button disabled={!canSubmitPublishRepository} onClick={submitPublishRepository}>
939939 {publishRepositoryAction.isPending ? (
940940 <>
941 <Spinner className="size-3.5" aria-hidden />
941 <Spinner size="sm" aria-hidden />
942942 Publishing...
943943 </>
944944 ) : (
apps/web/src/components/ProviderUpdateEnvironmentRows.tsx+1-1
......@@ -118,7 +118,7 @@ function EnvironmentUpdateRow({
118118 let trailing: ReactNode;
119119 switch (status.kind) {
120120 case "loading":
121 trailing = <Spinner className="size-4 text-muted-foreground" />;
121 trailing = <Spinner size="md" tone="muted" />;
122122 break;
123123 case "success":
124124 trailing = <CheckIcon aria-hidden="true" className="size-4 text-success" />;
apps/web/src/components/PullRequestThreadDialog.tsx+1-1
......@@ -252,7 +252,7 @@ export function PullRequestThreadDialog({
252252
253253 {isResolving ? (
254254 <div className="flex items-center gap-2 text-muted-foreground text-xs">
255 <Spinner className="size-3.5" />
255 <Spinner size="sm" />
256256 Resolving {terminology.singular}...
257257 </div>
258258 ) : null}
apps/web/src/components/chat/ComposerPrimaryActions.tsx+1-1
......@@ -254,7 +254,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
254254 </span>
255255 ) : null}
256256 {isConnecting || isSendBusy ? (
257 <Spinner className="size-3.5" aria-hidden="true" />
257 <Spinner size="sm" aria-hidden="true" />
258258 ) : (
259259 <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
260260 <path
apps/web/src/components/chat/MessagesTimeline.tsx+1-1
......@@ -2567,7 +2567,7 @@ function BackgroundWorktreeSetupChip({ snapshot }: { snapshot: WorktreeSetupSnap
25672567 />
25682568 }
25692569 >
2570 <Spinner className="size-3 shrink-0" />
2570 <Spinner size="xs" className="shrink-0" />
25712571 <span className="truncate">{scriptName}</span>
25722572 </PopoverTrigger>
25732573 <PopoverPopup
apps/web/src/components/chat/WorktreeSetupCard.tsx+1-1
......@@ -61,7 +61,7 @@ function StageIcon({ status }: { status: WorktreeSetupStage["status"] }) {
6161 case "done":
6262 return <CheckIcon aria-hidden className={className} />;
6363 case "running":
64 return <Spinner className={className} />;
64 return <Spinner size="md" className="shrink-0" />;
6565 case "failed":
6666 return <XIcon aria-hidden className={className} />;
6767 case "warning":
apps/web/src/components/clerk/ClerkUserProfilePage.tsx+1-1
......@@ -56,7 +56,7 @@ export function ClerkUserProfileRefreshButton({
5656 disabled={disabled || isPending}
5757 onClick={onClick}
5858 >
59 <RefreshIcon aria-hidden="true" className="size-3.5" refreshing={isPending} />
59 <RefreshIcon aria-hidden="true" size="sm" refreshing={isPending} />
6060 Refresh
6161 </Button>
6262 );
apps/web/src/components/device/DeviceLoadingView.tsx+1-1
......@@ -27,7 +27,7 @@ export function DeviceLoadingView(props: {
2727 ) : null}
2828 </div>
2929 <div className="flex items-center gap-2 text-xs text-muted-foreground">
30 {!props.error ? <Spinner className="size-3" /> : null}
30 {!props.error ? <Spinner size="xs" /> : null}
3131 <span>{props.message}</span>
3232 </div>
3333 {props.children}
apps/web/src/components/device/DevicePanel.tsx+1-1
......@@ -367,7 +367,7 @@ export function DevicePanel(props: {
367367 onClick={() => void selectDevice(deviceKey(device))}
368368 action={
369369 pendingDeviceKey === deviceKey(device) ? (
370 <Spinner className="size-3" />
370 <Spinner size="xs" />
371371 ) : (
372372 <span className="text-xs text-muted-foreground">
373373 {device.booted ? "Open" : "Start"}
apps/web/src/components/device/DeviceSetup.tsx+3-3
......@@ -203,7 +203,7 @@ export function DeviceHubSetupStatus({
203203 if (!pending && state.hostStatus !== "ready") return null;
204204 return (
205205 <p role="status" className="flex items-center gap-2 text-xs text-muted-foreground">
206 {pending ? <Spinner className="size-3" /> : <Check className="size-3 text-success" />}
206 {pending ? <Spinner size="xs" /> : <Check className="size-3 text-success" />}
207207 {pending
208208 ? state.hostStatus === "installing"
209209 ? compact
......@@ -235,7 +235,7 @@ function DevicePlatformSetup(props: {
235235 You can use either platform. Fixing a missing platform does not block the other one.
236236 </p>
237237 <Button size="compact" variant="outline" disabled={props.disabled} onClick={props.onCheck}>
238 {props.checking ? <Spinner className="size-3" /> : null}
238 {props.checking ? <Spinner size="xs" /> : null}
239239 {props.checking ? "Checking…" : "Check again"}
240240 </Button>
241241 </div>
......@@ -262,7 +262,7 @@ export function AgentDeviceSetupStatus(props: {
262262 : "Updating agent access…";
263263 return (
264264 <p role="status" className="flex items-center gap-2 text-xs text-muted-foreground">
265 <Spinner className="size-3" />
265 <Spinner size="xs" />
266266 {label}
267267 </p>
268268 );
apps/web/src/components/device/DeviceToolsPanel.tsx+2-2
......@@ -173,7 +173,7 @@ export function DeviceToolsPanel(props: {
173173 >
174174 <div className="flex h-9 shrink-0 items-center gap-2 border-b px-3">
175175 <span className="font-medium">Tools</span>
176 {pending ? <Spinner className="size-3.5" /> : null}
176 {pending ? <Spinner size="sm" /> : null}
177177 <Button
178178 size="icon-xs"
179179 variant="ghost-muted"
......@@ -190,7 +190,7 @@ export function DeviceToolsPanel(props: {
190190 ) : null}
191191 {detail === null && !error ? (
192192 <div className="flex items-center gap-2 px-3 py-3 text-xs text-muted-foreground">
193 <Spinner className="size-3.5" /> Reading device settings…
193 <Spinner size="sm" /> Reading device settings…
194194 </div>
195195 ) : null}
196196
apps/web/src/components/files/FilePreviewPanel.tsx+3-3
......@@ -169,7 +169,7 @@ function WorkspaceImagePreview(props: {
169169 </div>
170170 ) : (
171171 <div className="flex min-h-0 flex-1 items-center justify-center text-muted-foreground">
172 <Spinner className="size-5" />
172 <Spinner size="lg" />
173173 </div>
174174 );
175175}
......@@ -214,7 +214,7 @@ function WorkspaceBrowserPreview(props: {
214214 if (assetUrl._tag !== "Success") {
215215 return (
216216 <div className="flex min-h-0 flex-1 items-center justify-center text-muted-foreground">
217 <Spinner className="size-5" />
217 <Spinner size="lg" />
218218 </div>
219219 );
220220 }
......@@ -1232,7 +1232,7 @@ export default function FilePreviewPanel({
12321232 </div>
12331233 ) : relativePath && file.data === null ? (
12341234 <div className="flex min-h-0 flex-1 items-center justify-center text-muted-foreground">
1235 <Spinner className="size-5" />
1235 <Spinner size="lg" />
12361236 </div>
12371237 ) : relativePath && file.data ? (
12381238 isMarkdown && renderMarkdown ? (
apps/web/src/components/files/fileSurfaceChrome.tsx+1-1
......@@ -131,7 +131,7 @@ export function FileSurfaceLoading(props: { readonly className?: string }) {
131131 props.className,
132132 )}
133133 >
134 <Spinner className="size-5" />
134 <Spinner size="lg" />
135135 </div>
136136 );
137137}
apps/web/src/components/onboarding/WelcomeWizard.tsx+2-2
......@@ -1162,7 +1162,7 @@ function ImportStep({
11621162 <div className="flex h-full min-h-40 flex-col">
11631163 <h1 className="text-2xl font-semibold tracking-tight text-foreground">Your projects</h1>
11641164 <div className="flex flex-1 flex-col items-center justify-center gap-3 py-6">
1165 <Spinner className="size-5 text-muted-foreground" />
1165 <Spinner size="lg" tone="muted" />
11661166 <p className="text-center text-sm text-muted-foreground">
11671167 Looking for projects from Claude Code and Codex…
11681168 </p>
......@@ -1229,7 +1229,7 @@ function ImportStep({
12291229 ) : null}
12301230 {scan.isPending && scan.data === null ? (
12311231 <div className="flex items-center gap-2 py-3 text-sm text-muted-foreground">
1232 <Spinner className="size-4" />
1232 <Spinner size="md" />
12331233 Looking for projects…
12341234 </div>
12351235 ) : scan.error !== null ? (
apps/web/src/components/pullRequest/PullRequestActivityUnavailableState.tsx+1-1
......@@ -23,7 +23,7 @@ export function PullRequestActivityUnavailableState({
2323 <p className="text-sm font-medium text-foreground">Could not load pull request activity</p>
2424 <p className="max-w-md text-xs text-muted-foreground">{error}</p>
2525 <Button size="sm" variant="outline" onClick={onRetry}>
26 <RefreshIcon aria-hidden className="size-3.5" />
26 <RefreshIcon aria-hidden size="sm" />
2727 Retry
2828 </Button>
2929 </div>
apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx+2-2
......@@ -2010,7 +2010,7 @@ export function PullRequestDetailPanel({
20102010 the spinning glyph in place of the dots: the reader sees the panel
20112011 is fetching without a control appearing or the row shifting. */}
20122012 {refreshing ? (
2013 <RefreshIcon refreshing className="size-4" />
2013 <RefreshIcon refreshing size="md" />
20142014 ) : (
20152015 <MoreHorizontalIcon className="size-4" />
20162016 )}
......@@ -2034,7 +2034,7 @@ export function PullRequestDetailPanel({
20342034 onPickerOpenChange={setThreadPickerOpen}
20352035 />
20362036 <MenuItem disabled={refreshing} onClick={() => void refreshFromHost()}>
2037 <RefreshIcon className="size-3.5" refreshing={refreshing} />
2037 <RefreshIcon size="sm" refreshing={refreshing} />
20382038 Refresh
20392039 </MenuItem>
20402040 <MenuItem disabled={handoff !== null} onClick={askAboutPullRequest}>
apps/web/src/components/pullRequest/PullRequestListEmptyState.tsx+2-2
......@@ -150,7 +150,7 @@ export function PullRequestListEmptyState({
150150 {/* The hosts answered this query once; a pull request opened since then would answer
151151 differently, and nothing on screen says which of the two the reader is looking at. */}
152152 <Button size="sm" variant="outline" disabled={refreshing} onClick={onRefresh}>
153 <RefreshIcon className="size-3.5" refreshing={refreshing} />
153 <RefreshIcon size="sm" refreshing={refreshing} />
154154 {refreshing ? "Checking..." : "Check again"}
155155 </Button>
156156 </EmptyContent>
......@@ -176,7 +176,7 @@ export function PullRequestListEmptyState({
176176 </Button>
177177 ) : null}
178178 <Button size="sm" variant="outline" disabled={refreshing} onClick={onRefresh}>
179 <RefreshIcon className="size-3.5" refreshing={refreshing} />
179 <RefreshIcon size="sm" refreshing={refreshing} />
180180 {refreshing ? "Checking..." : "Check again"}
181181 </Button>
182182 </EmptyContent>
apps/web/src/components/pullRequest/PullRequestsUnavailableState.tsx+1-1
......@@ -46,7 +46,7 @@ export function PullRequestsUnavailableState({
4646 disabled={refreshing}
4747 aria-busy={refreshing}
4848 >
49 <RefreshIcon className="size-3.5" refreshing={refreshing} />
49 <RefreshIcon size="sm" refreshing={refreshing} />
5050 Retry
5151 </Button>
5252 ) : null}
apps/web/src/components/search/ProjectContentSearchDialog.tsx+1-1
......@@ -226,7 +226,7 @@ function OpenContentSearchDialog(props: {
226226 <div className="flex h-9 shrink-0 items-center border-b px-3 text-xs text-muted-foreground">
227227 {search.isPending ? (
228228 <span className="flex items-center gap-2">
229 <Spinner className="size-3.5" /> Searching…
229 <Spinner size="sm" /> Searching…
230230 </span>
231231 ) : search.error ? (
232232 <span className="text-destructive">{search.error}</span>
apps/web/src/components/settings/BrowserImportWizard.tsx+2-2
......@@ -491,7 +491,7 @@ function ImportingStep() {
491491 <DialogDescription>This may take a moment.</DialogDescription>
492492 </DialogHeader>
493493 <DialogPanel className="flex items-center gap-3 py-6">
494 <Spinner className="size-4 text-muted-foreground" />
494 <Spinner size="md" tone="muted" />
495495 <span className="text-sm text-muted-foreground">Importing…</span>
496496 </DialogPanel>
497497 </>
......@@ -516,7 +516,7 @@ function CheckingStep({
516516 </DialogDescription>
517517 </DialogHeader>
518518 <DialogPanel className="flex items-center gap-3 py-6">
519 <Spinner className="size-4 text-muted-foreground" />
519 <Spinner size="md" tone="muted" />
520520 <span className="text-sm text-muted-foreground">
521521 {check === "fullDiskAccess" ? "Checking access…" : "Checking…"}
522522 </span>
apps/web/src/components/settings/ConnectionsSettings.tsx+6-6
......@@ -3435,7 +3435,7 @@ export function ConnectionsSettings() {
34353435 pendingDesktopServerExposureMode === null || isUpdatingDesktopServerExposure
34363436 }
34373437 >
3438 {isUpdatingDesktopServerExposure && <Spinner className="size-3.5" />}
3438 {isUpdatingDesktopServerExposure && <Spinner size="sm" />}
34393439 <span className="[text-box:trim-both_cap_alphabetic]">
34403440 {isUpdatingDesktopServerExposure
34413441 ? "Restarting…"
......@@ -3499,7 +3499,7 @@ export function ConnectionsSettings() {
34993499 >
35003500 {isUpdatingWslBackend ? (
35013501 <>
3502 <Spinner className="size-3.5" />
3502 <Spinner size="sm" />
35033503 Applying…
35043504 </>
35053505 ) : (
......@@ -3513,7 +3513,7 @@ export function ConnectionsSettings() {
35133513 >
35143514 {isUpdatingWslBackend ? (
35153515 <>
3516 <Spinner className="size-3.5" />
3516 <Spinner size="sm" />
35173517 Applying…
35183518 </>
35193519 ) : (
......@@ -3534,7 +3534,7 @@ export function ConnectionsSettings() {
35343534 >
35353535 {isUpdatingWslBackend ? (
35363536 <>
3537 <Spinner className="size-3.5" />
3537 <Spinner size="sm" />
35383538 Applying…
35393539 </>
35403540 ) : pendingWslChange?.kind === "disable" ? (
......@@ -3583,7 +3583,7 @@ export function ConnectionsSettings() {
35833583 >
35843584 {isUpdatingTailscaleServe ? (
35853585 <>
3586 <Spinner className="size-3.5" />
3586 <Spinner size="sm" />
35873587 Restarting…
35883588 </>
35893589 ) : (
......@@ -3657,7 +3657,7 @@ export function ConnectionsSettings() {
36573657 >
36583658 {isUpdatingTailscaleServe ? (
36593659 <>
3660 <Spinner className="size-3.5" />
3660 <Spinner size="sm" />
36613661 Restarting…
36623662 </>
36633663 ) : (
apps/web/src/components/settings/DeviceHostEditor.tsx+3-3
......@@ -156,7 +156,7 @@ export function DeviceHostEditor({
156156 if (Option.isSome(input)) void testConnection(input.value);
157157 }}
158158 >
159 {checking ? <Spinner className="size-3" /> : null} Test connection
159 {checking ? <Spinner size="xs" /> : null} Test connection
160160 </Button>
161161 </div>
162162 {results ? (
......@@ -173,7 +173,7 @@ export function DeviceHostEditor({
173173 >
174174 {result.status === "pending" ? (
175175 <>
176 <Spinner className="size-3" /> Checking…
176 <Spinner size="xs" /> Checking…
177177 </>
178178 ) : result.status === "local" ? (
179179 <>
......@@ -215,7 +215,7 @@ export function DeviceHostEditor({
215215 Cancel
216216 </Button>
217217 <Button type="submit" disabled={busy || checking || !valid || !draft.label.trim()}>
218 {busy ? <Spinner className="size-3" /> : null} Save host
218 {busy ? <Spinner size="xs" /> : null} Save host
219219 </Button>
220220 </DialogFooter>
221221 </DialogPopup>
apps/web/src/components/settings/DeviceHostsSettings.tsx+1-1
......@@ -267,7 +267,7 @@ function DeviceHostList({
267267 role="status"
268268 className="inline-flex items-center gap-1.5 text-xs text-muted-foreground"
269269 >
270 <Spinner className="size-3" />
270 <Spinner size="xs" />
271271 {progress}
272272 </span>
273273 ) : null}
apps/web/src/components/settings/LocalEnvironmentSetting.tsx+1-1
......@@ -86,7 +86,7 @@ export function LocalEnvironmentSetting() {
8686 >
8787 {isUpdating ? (
8888 <>
89 <Spinner className="size-3.5" />
89 <Spinner size="sm" />
9090 Restarting…
9191 </>
9292 ) : enabled ? (
apps/web/src/components/settings/ResourceTelemetryDiagnostics.tsx+3-3
......@@ -976,7 +976,7 @@ export function ResourceTelemetryDiagnostics({
976976 onClick={telemetry.refresh}
977977 aria-label="Refresh resource telemetry"
978978 >
979 <RefreshIcon className="size-3" refreshing={telemetry.isPending} />
979 <RefreshIcon size="xs" refreshing={telemetry.isPending} />
980980 </Button>
981981 }
982982 />
......@@ -1087,7 +1087,7 @@ export function ResourceTelemetryDiagnostics({
10871087 headerAction={
10881088 collectorNeedsRetry ? (
10891089 <Button size="xs" variant="outline" disabled={isRetrying} onClick={retryCollector}>
1090 <RefreshIcon className="size-3" refreshing={isRetrying} />
1090 <RefreshIcon size="xs" refreshing={isRetrying} />
10911091 Retry monitor
10921092 </Button>
10931093 ) : null
......@@ -1225,7 +1225,7 @@ export function ResourceTelemetryDiagnostics({
12251225 onClick={history.refresh}
12261226 aria-label="Refresh resource history"
12271227 >
1228 <RefreshIcon className="size-3" refreshing={history.isPending} />
1228 <RefreshIcon size="xs" refreshing={history.isPending} />
12291229 </Button>
12301230 </div>
12311231 }
apps/web/src/components/settings/SettingsPanels.tsx+1-1
......@@ -3359,7 +3359,7 @@ export function ArchivedThreadsPanel() {
33593359 title={
33603360 <span className="inline-flex items-center gap-2">
33613361 {isLoadingArchive ? (
3362 <Spinner className="size-3.5 text-muted-foreground" />
3362 <Spinner size="sm" tone="muted" />
33633363 ) : (
33643364 <ArchiveIcon className="size-3.5 text-muted-foreground" />
33653365 )}
apps/web/src/components/settings/SourceControlSettings.tsx+1-1
......@@ -491,7 +491,7 @@ function EmptySourceControlDiscovery({
491491 </EmptyHeader>
492492 <EmptyContent>
493493 <Button size="sm" variant="outline" onClick={onScan} disabled={isPending}>
494 <RefreshIcon className="size-3.5" refreshing={isPending} />
494 <RefreshIcon size="sm" refreshing={isPending} />
495495 Scan
496496 </Button>
497497 </EmptyContent>
apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx+1-1
......@@ -174,7 +174,7 @@ export function SidebarProviderUpdatePill() {
174174 onClick={openProviderSettings}
175175 >
176176 {displayedView.tone === "loading" ? (
177 <Spinner className="size-3.5 shrink-0" />
177 <Spinner size="sm" className="shrink-0" />
178178 ) : displayedView.tone === "success" ? (
179179 <CircleCheckIcon className="size-3.5 shrink-0" />
180180 ) : displayedView.tone === "error" ? (
apps/web/src/components/ui/refresh-icon.tsx+21-2
......@@ -1,19 +1,38 @@
11import { RefreshCwIcon } from "lucide-react";
2import { cva, type VariantProps } from "class-variance-authority";
23
34import { cn } from "~/lib/utils";
45import { observeVisibleAnimation } from "~/lib/visibleAnimation";
56
7// No default size: inside a Button the parent's svg rule sizes the glyph.
8const refreshIconVariants = cva("", {
9 variants: {
10 size: {
11 xs: "size-3",
12 sm: "size-3.5",
13 md: "size-4",
14 lg: "size-5",
15 },
16 },
17});
18
619/** Keep the refresh glyph in place while its owning action is running. */
720export function RefreshIcon({
821 refreshing = false,
922 className,
23 size,
1024 ...props
11}: React.ComponentPropsWithoutRef<typeof RefreshCwIcon> & { refreshing?: boolean }) {
25}: React.ComponentPropsWithoutRef<typeof RefreshCwIcon> &
26 VariantProps<typeof refreshIconVariants> & { refreshing?: boolean }) {
1227 return (
1328 <RefreshCwIcon
1429 aria-hidden
1530 ref={refreshing ? observeVisibleAnimation : undefined}
16 className={cn(refreshing && "motion-safe:visible-animate-spin", className)}
31 className={cn(
32 refreshIconVariants({ size }),
33 refreshing && "motion-safe:visible-animate-spin",
34 className,
35 )}
1736 {...props}
1837 />
1938 );
apps/web/src/components/ui/spinner.tsx+25-2
......@@ -1,13 +1,36 @@
11import { LoaderCircleIcon } from "lucide-react";
2import { cva, type VariantProps } from "class-variance-authority";
23import { observeVisibleAnimation } from "~/lib/visibleAnimation";
34import { cn } from "~/lib/utils";
45
5function Spinner({ className, ...props }: React.ComponentPropsWithoutRef<typeof LoaderCircleIcon>) {
6// No default size: inside a Button the parent's svg rule sizes the glyph.
7const spinnerVariants = cva("motion-safe:visible-animate-spin", {
8 variants: {
9 size: {
10 xs: "size-3",
11 sm: "size-3.5",
12 md: "size-4",
13 lg: "size-5",
14 },
15 tone: {
16 current: "",
17 muted: "text-muted-foreground",
18 },
19 },
20 defaultVariants: { tone: "current" },
21});
22
23function Spinner({
24 className,
25 size,
26 tone,
27 ...props
28}: React.ComponentPropsWithoutRef<typeof LoaderCircleIcon> & VariantProps<typeof spinnerVariants>) {
629 return (
730 <LoaderCircleIcon
831 aria-label="Loading"
932 ref={observeVisibleAnimation}
10 className={cn("motion-safe:visible-animate-spin", className)}
33 className={cn(spinnerVariants({ size, tone }), className)}
1134 role="status"
1235 {...props}
1336 />
apps/web/src/components/usage/UsagePage.tsx+2-2
......@@ -300,7 +300,7 @@ export function UsagePage() {
300300 size="icon-sm"
301301 variant="ghost"
302302 >
303 <RefreshIcon className="size-3.5" refreshing={isRefreshing} />
303 <RefreshIcon size="sm" refreshing={isRefreshing} />
304304 </Button>
305305 </div>
306306 <div className="col-span-2 ms-auto flex min-w-0 items-center justify-end gap-1 xl:hidden">
......@@ -359,7 +359,7 @@ export function UsagePage() {
359359 size="icon-sm"
360360 variant="ghost"
361361 >
362 <RefreshIcon className="size-3.5" refreshing={isRefreshing} />
362 <RefreshIcon size="sm" refreshing={isRefreshing} />
363363 </Button>
364364 </div>
365365 </div>
apps/web/src/routes/_chat.index.tsx+1-1
......@@ -99,7 +99,7 @@ function DraftStartError({ onRetry }: { readonly onRetry: () => void }) {
9999 </EmptyDescription>
100100 <div className="mt-5 flex justify-center">
101101 <Button size="sm" onClick={onRetry}>
102 <RefreshIcon className="size-4" />
102 <RefreshIcon size="md" />
103103 Try again
104104 </Button>
105105 </div>
apps/web/src/routes/_chat.pull-requests.tsx+2-2
......@@ -1830,7 +1830,7 @@ function PullRequestsRouteView() {
18301830 <div className="flex justify-center py-3 text-xs text-muted-foreground">
18311831 {loadingMore ? (
18321832 <span className="flex items-center gap-2">
1833 <Spinner aria-hidden className="size-3.5" />
1833 <Spinner aria-hidden size="sm" />
18341834 {sentCursors === null ? "Updating pull requests" : "Loading more"}
18351835 </span>
18361836 ) : canContinue || pageSize < MAX_PAGE_SIZE ? (
......@@ -2592,7 +2592,7 @@ function PullRequestRefreshControl({
25922592 onClick={onRefresh}
25932593 disabled={refreshing}
25942594 >
2595 <RefreshIcon className="size-4" refreshing={refreshing} />
2595 <RefreshIcon size="md" refreshing={refreshing} />
25962596 </Button>
25972597 );
25982598}
scripts/lint-restyle-ceiling.ts+1-1
......@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";
88// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the
99// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you
1010// migrate a file, and delete this script when the rule becomes an error.
11export const RESTYLE_CEILING = 1214;
11export const RESTYLE_CEILING = 1207;
1212
1313const RULE = "shadcn(no-restyle)";
1414