authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-22 12:22:14-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-22 12:22:14-07:00
log7d658c24dbf81d7cfc594b89d9cd5b8bab0e1046
tree75d85a97ff2900fca24cc79a9ee383cfa1541cb5
parent8d02447c36df2cf6cbfe1501738314685a22a1fc
signature Signed by PGP key B5690EEEBB952194

refactor(web): give Skeleton three shapes (#13030)

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

7 files changed, 48 insertions(+), 32 deletions(-)

apps/web/src/components/DiffPanelShell.tsx+8-8
......@@ -50,13 +50,13 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str
5050 return (
5151 <div className="flex h-8 items-center gap-2 px-2 pr-3">
5252 <div className="flex size-5 shrink-0 items-center justify-center">
53 <Skeleton className="size-2.5 rounded-[2px]" />
53 <Skeleton className="size-2.5" />
5454 </div>
55 <Skeleton className="size-5 shrink-0 rounded-md" />
56 <Skeleton className={cn("h-3 rounded-full", titleClassName)} />
55 <Skeleton className="size-5 shrink-0" />
56 <Skeleton shape="pill" className={cn("h-3", titleClassName)} />
5757 <div className="ml-auto flex shrink-0 items-center gap-2">
58 <Skeleton className="h-3 w-5 rounded-full" />
59 <Skeleton className="h-3 w-5 rounded-full" />
58 <Skeleton shape="pill" className="h-3 w-5" />
59 <Skeleton shape="pill" className="h-3 w-5" />
6060 </div>
6161 </div>
6262 );
......@@ -65,8 +65,8 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str
6565function DiffCodeLineSkeleton({ contentClassName }: { contentClassName: string }) {
6666 return (
6767 <div className="flex items-center gap-3">
68 <Skeleton className="h-2.5 w-5 shrink-0 rounded-full" />
69 <Skeleton className={cn("h-2.5 rounded-full", contentClassName)} />
68 <Skeleton shape="pill" className="h-2.5 w-5 shrink-0" />
69 <Skeleton shape="pill" className={cn("h-2.5", contentClassName)} />
7070 </div>
7171 );
7272}
......@@ -82,7 +82,7 @@ export function DiffPanelLoadingState(props: { label: string }) {
8282 <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" />
8383 <div className="flex h-6 items-center gap-2 px-2 pr-3">
8484 <div className="h-px flex-1 bg-border/40" />
85 <Skeleton className="h-2.5 w-24 rounded-full" />
85 <Skeleton shape="pill" className="h-2.5 w-24" />
8686 <div className="h-px flex-1 bg-border/40" />
8787 </div>
8888 <div className="space-y-2 px-3 py-2">
apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx+1-1
......@@ -69,7 +69,7 @@ function MobileClientsSkeleton() {
6969 {MOBILE_CLIENT_SKELETON_ROWS.map((row) => (
7070 <div key={row} className="py-4">
7171 <div className="flex gap-3">
72 <Skeleton className="size-8 shrink-0 rounded-lg" />
72 <Skeleton shape="card" className="size-8 shrink-0" />
7373 <div className="flex-1 space-y-2">
7474 <Skeleton className="h-3.5 w-36" />
7575 <Skeleton className="h-3 w-28" />
apps/web/src/components/cloud/CloudEnvironmentConnectList.tsx+3-3
......@@ -57,10 +57,10 @@ function RemoteEnvironmentRowsSkeleton() {
5757 <div className={ITEM_ROW_CLASSNAME}>
5858 <div className={ITEM_ROW_INNER_CLASSNAME}>
5959 <div className="min-w-0 flex-1 space-y-2">
60 <Skeleton className="h-4 w-32 rounded-full" />
61 <Skeleton className="h-3 w-20 rounded-full" />
60 <Skeleton shape="pill" className="h-4 w-32" />
61 <Skeleton shape="pill" className="h-3 w-20" />
6262 </div>
63 <Skeleton className="h-7 w-16 rounded-md" />
63 <Skeleton className="h-7 w-16" />
6464 </div>
6565 </div>
6666 );
apps/web/src/components/settings/SourceControlSettings.tsx+8-7
......@@ -440,20 +440,21 @@ function SourceControlSectionSkeleton({
440440 <div className="min-w-0 flex-1 space-y-2">
441441 <div className="flex items-center gap-2">
442442 <span className="relative inline-flex size-5 shrink-0 items-center justify-center">
443 <Skeleton className="size-4.5 rounded-md" />
443 <Skeleton className="size-4.5" />
444444 <Skeleton
445 className="pointer-events-none absolute -left-0.5 -top-0.5 size-2 rounded-full ring-2 ring-background"
445 shape="pill"
446 className="pointer-events-none absolute -left-0.5 -top-0.5 size-2"
446447 aria-hidden
447448 />
448449 </span>
449 <Skeleton className="h-4 w-28 rounded-full" />
450 <Skeleton className="h-5 w-14 rounded-full" />
450 <Skeleton shape="pill" className="h-4 w-28" />
451 <Skeleton shape="pill" className="h-5 w-14" />
451452 </div>
452 <Skeleton className="h-3 w-full max-w-xs rounded-full" />
453 <Skeleton shape="pill" className="h-3 w-full max-w-xs" />
453454 </div>
454455 <div className="flex shrink-0 items-center gap-2">
455 <Skeleton className="size-7 rounded-md" />
456 <Skeleton className="h-5 w-9 rounded-full" />
456 <Skeleton className="size-7" />
457 <Skeleton shape="pill" className="h-5 w-9" />
457458 </div>
458459 </div>
459460 </div>
apps/web/src/components/ui/skeleton.tsx+21-6
......@@ -1,12 +1,27 @@
1import { cva, type VariantProps } from "class-variance-authority";
2
13import { cn } from "~/lib/utils";
24
3function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
5// A skeleton's width and height are its content, so consumers size it through
6// className (the lint contract allows layout there). Its shape is not.
7const skeletonVariants = cva("bg-muted-foreground/15 motion-safe:animate-skeleton", {
8 variants: {
9 shape: {
10 block: "rounded-sm",
11 card: "rounded-lg",
12 pill: "rounded-full",
13 },
14 },
15 defaultVariants: { shape: "block" },
16});
17
18function Skeleton({
19 className,
20 shape,
21 ...props
22}: React.ComponentProps<"div"> & VariantProps<typeof skeletonVariants>) {
423 return (
5 <div
6 className={cn("rounded-sm bg-muted-foreground/15 motion-safe:animate-skeleton", className)}
7 data-slot="skeleton"
8 {...props}
9 />
24 <div className={cn(skeletonVariants({ shape }), className)} data-slot="skeleton" {...props} />
1025 );
1126}
1227
apps/web/src/components/usage/UsagePage.tsx+6-6
......@@ -868,8 +868,8 @@ function UsageSkeleton() {
868868 <div key={provider} className="flex flex-col gap-1">
869869 <div className="flex min-h-5 items-center justify-between gap-4">
870870 <span className="flex items-center gap-2">
871 <Skeleton className="size-2 shrink-0 rounded-full" />
872 <Skeleton className="size-4 shrink-0 rounded-full" />
871 <Skeleton shape="pill" className="size-2 shrink-0" />
872 <Skeleton shape="pill" className="size-4 shrink-0" />
873873 <Skeleton className="h-3.5 w-20" />
874874 </span>
875875 <Skeleton className="h-3.5 w-14" />
......@@ -882,8 +882,8 @@ function UsageSkeleton() {
882882 <div className="flex flex-col gap-3">
883883 <Skeleton className="h-5 w-24" />
884884 <div className="flex flex-col gap-1">
885 <Skeleton className="ml-16 h-56 bg-muted-foreground/10" />
886 <Skeleton className="ml-16 h-4 bg-muted-foreground/10" />
885 <Skeleton className="ml-16 h-56" />
886 <Skeleton className="ml-16 h-4" />
887887 </div>
888888 </div>
889889 </section>
......@@ -905,9 +905,9 @@ function UsageSkeleton() {
905905 <section className="flex flex-col gap-3">
906906 <div className="flex items-center justify-between gap-3">
907907 <h2 className="text-sm font-medium text-foreground">Breakdown</h2>
908 <Skeleton className="h-7 w-28 rounded-lg" />
908 <Skeleton shape="card" className="h-7 w-28" />
909909 </div>
910 <Skeleton className="h-44 bg-muted-foreground/10" />
910 <Skeleton className="h-44" />
911911 </section>
912912 </>
913913 );
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 = 1008;
11export const RESTYLE_CEILING = 981;
1212
1313const RULE = "shadcn(no-restyle)";
1414