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...@@ -50,13 +50,13 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str
50 return (50 return (
51 <div className="flex h-8 items-center gap-2 px-2 pr-3">51 <div className="flex h-8 items-center gap-2 px-2 pr-3">
52 <div className="flex size-5 shrink-0 items-center justify-center">52 <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" />
54 </div>54 </div>
55 <Skeleton className="size-5 shrink-0 rounded-md" />55 <Skeleton className="size-5 shrink-0" />
56 <Skeleton className={cn("h-3 rounded-full", titleClassName)} />56 <Skeleton shape="pill" className={cn("h-3", titleClassName)} />
57 <div className="ml-auto flex shrink-0 items-center gap-2">57 <div className="ml-auto flex shrink-0 items-center gap-2">
58 <Skeleton className="h-3 w-5 rounded-full" />58 <Skeleton shape="pill" className="h-3 w-5" />
59 <Skeleton className="h-3 w-5 rounded-full" />59 <Skeleton shape="pill" className="h-3 w-5" />
60 </div>60 </div>
61 </div>61 </div>
62 );62 );
...@@ -65,8 +65,8 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str...@@ -65,8 +65,8 @@ export function DiffFileHeaderSkeleton({ titleClassName }: { titleClassName: str
65function DiffCodeLineSkeleton({ contentClassName }: { contentClassName: string }) {65function DiffCodeLineSkeleton({ contentClassName }: { contentClassName: string }) {
66 return (66 return (
67 <div className="flex items-center gap-3">67 <div className="flex items-center gap-3">
68 <Skeleton className="h-2.5 w-5 shrink-0 rounded-full" />68 <Skeleton shape="pill" className="h-2.5 w-5 shrink-0" />
69 <Skeleton className={cn("h-2.5 rounded-full", contentClassName)} />69 <Skeleton shape="pill" className={cn("h-2.5", contentClassName)} />
70 </div>70 </div>
71 );71 );
72}72}
...@@ -82,7 +82,7 @@ export function DiffPanelLoadingState(props: { label: string }) {...@@ -82,7 +82,7 @@ export function DiffPanelLoadingState(props: { label: string }) {
82 <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" />82 <DiffFileHeaderSkeleton titleClassName="w-1/2 max-w-64" />
83 <div className="flex h-6 items-center gap-2 px-2 pr-3">83 <div className="flex h-6 items-center gap-2 px-2 pr-3">
84 <div className="h-px flex-1 bg-border/40" />84 <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" />
86 <div className="h-px flex-1 bg-border/40" />86 <div className="h-px flex-1 bg-border/40" />
87 </div>87 </div>
88 <div className="space-y-2 px-3 py-2">88 <div className="space-y-2 px-3 py-2">
apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx+1-1
...@@ -69,7 +69,7 @@ function MobileClientsSkeleton() {...@@ -69,7 +69,7 @@ function MobileClientsSkeleton() {
69 {MOBILE_CLIENT_SKELETON_ROWS.map((row) => (69 {MOBILE_CLIENT_SKELETON_ROWS.map((row) => (
70 <div key={row} className="py-4">70 <div key={row} className="py-4">
71 <div className="flex gap-3">71 <div className="flex gap-3">
72 <Skeleton className="size-8 shrink-0 rounded-lg" />72 <Skeleton shape="card" className="size-8 shrink-0" />
73 <div className="flex-1 space-y-2">73 <div className="flex-1 space-y-2">
74 <Skeleton className="h-3.5 w-36" />74 <Skeleton className="h-3.5 w-36" />
75 <Skeleton className="h-3 w-28" />75 <Skeleton className="h-3 w-28" />
apps/web/src/components/cloud/CloudEnvironmentConnectList.tsx+3-3
...@@ -57,10 +57,10 @@ function RemoteEnvironmentRowsSkeleton() {...@@ -57,10 +57,10 @@ function RemoteEnvironmentRowsSkeleton() {
57 <div className={ITEM_ROW_CLASSNAME}>57 <div className={ITEM_ROW_CLASSNAME}>
58 <div className={ITEM_ROW_INNER_CLASSNAME}>58 <div className={ITEM_ROW_INNER_CLASSNAME}>
59 <div className="min-w-0 flex-1 space-y-2">59 <div className="min-w-0 flex-1 space-y-2">
60 <Skeleton className="h-4 w-32 rounded-full" />60 <Skeleton shape="pill" className="h-4 w-32" />
61 <Skeleton className="h-3 w-20 rounded-full" />61 <Skeleton shape="pill" className="h-3 w-20" />
62 </div>62 </div>
63 <Skeleton className="h-7 w-16 rounded-md" />63 <Skeleton className="h-7 w-16" />
64 </div>64 </div>
65 </div>65 </div>
66 );66 );
apps/web/src/components/settings/SourceControlSettings.tsx+8-7
...@@ -440,20 +440,21 @@ function SourceControlSectionSkeleton({...@@ -440,20 +440,21 @@ function SourceControlSectionSkeleton({
440 <div className="min-w-0 flex-1 space-y-2">440 <div className="min-w-0 flex-1 space-y-2">
441 <div className="flex items-center gap-2">441 <div className="flex items-center gap-2">
442 <span className="relative inline-flex size-5 shrink-0 items-center justify-center">442 <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" />
444 <Skeleton444 <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"
446 aria-hidden447 aria-hidden
447 />448 />
448 </span>449 </span>
449 <Skeleton className="h-4 w-28 rounded-full" />450 <Skeleton shape="pill" className="h-4 w-28" />
450 <Skeleton className="h-5 w-14 rounded-full" />451 <Skeleton shape="pill" className="h-5 w-14" />
451 </div>452 </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" />
453 </div>454 </div>
454 <div className="flex shrink-0 items-center gap-2">455 <div className="flex shrink-0 items-center gap-2">
455 <Skeleton className="size-7 rounded-md" />456 <Skeleton className="size-7" />
456 <Skeleton className="h-5 w-9 rounded-full" />457 <Skeleton shape="pill" className="h-5 w-9" />
457 </div>458 </div>
458 </div>459 </div>
459 </div>460 </div>
apps/web/src/components/ui/skeleton.tsx+21-6
...@@ -1,12 +1,27 @@...@@ -1,12 +1,27 @@
1import { cva, type VariantProps } from "class-variance-authority";
2
1import { cn } from "~/lib/utils";3import { 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>) {
4 return (23 return (
5 <div24 <div className={cn(skeletonVariants({ shape }), className)} data-slot="skeleton" {...props} />
6 className={cn("rounded-sm bg-muted-foreground/15 motion-safe:animate-skeleton", className)}
7 data-slot="skeleton"
8 {...props}
9 />
10 );25 );
11}26}
1227
apps/web/src/components/usage/UsagePage.tsx+6-6
...@@ -868,8 +868,8 @@ function UsageSkeleton() {...@@ -868,8 +868,8 @@ function UsageSkeleton() {
868 <div key={provider} className="flex flex-col gap-1">868 <div key={provider} className="flex flex-col gap-1">
869 <div className="flex min-h-5 items-center justify-between gap-4">869 <div className="flex min-h-5 items-center justify-between gap-4">
870 <span className="flex items-center gap-2">870 <span className="flex items-center gap-2">
871 <Skeleton className="size-2 shrink-0 rounded-full" />871 <Skeleton shape="pill" className="size-2 shrink-0" />
872 <Skeleton className="size-4 shrink-0 rounded-full" />872 <Skeleton shape="pill" className="size-4 shrink-0" />
873 <Skeleton className="h-3.5 w-20" />873 <Skeleton className="h-3.5 w-20" />
874 </span>874 </span>
875 <Skeleton className="h-3.5 w-14" />875 <Skeleton className="h-3.5 w-14" />
...@@ -882,8 +882,8 @@ function UsageSkeleton() {...@@ -882,8 +882,8 @@ function UsageSkeleton() {
882 <div className="flex flex-col gap-3">882 <div className="flex flex-col gap-3">
883 <Skeleton className="h-5 w-24" />883 <Skeleton className="h-5 w-24" />
884 <div className="flex flex-col gap-1">884 <div className="flex flex-col gap-1">
885 <Skeleton className="ml-16 h-56 bg-muted-foreground/10" />885 <Skeleton className="ml-16 h-56" />
886 <Skeleton className="ml-16 h-4 bg-muted-foreground/10" />886 <Skeleton className="ml-16 h-4" />
887 </div>887 </div>
888 </div>888 </div>
889 </section>889 </section>
...@@ -905,9 +905,9 @@ function UsageSkeleton() {...@@ -905,9 +905,9 @@ function UsageSkeleton() {
905 <section className="flex flex-col gap-3">905 <section className="flex flex-col gap-3">
906 <div className="flex items-center justify-between gap-3">906 <div className="flex items-center justify-between gap-3">
907 <h2 className="text-sm font-medium text-foreground">Breakdown</h2>907 <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" />
909 </div>909 </div>
910 <Skeleton className="h-44 bg-muted-foreground/10" />910 <Skeleton className="h-44" />
911 </section>911 </section>
912 </>912 </>
913 );913 );
scripts/lint-restyle-ceiling.ts+1-1
...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";...@@ -8,7 +8,7 @@ import * as NodeURL from "node:url";
8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the8// components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the
9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you9// count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you
10// migrate a file, and delete this script when the rule becomes an error.10// migrate a file, and delete this script when the rule becomes an error.
11export const RESTYLE_CEILING = 1008;11export const RESTYLE_CEILING = 981;
1212
13const RULE = "shadcn(no-restyle)";13const RULE = "shadcn(no-restyle)";
1414