web/app/components/skeleton.tsxblame
View source
9e346cc1interface SkeletonProps {
9e346cc2 className?: string;
9e346cc3 width?: string;
9e346cc4 height?: string;
9e346cc5}
9e346cc6
9e346cc7export function Skeleton({ className = "", width, height = "0.875rem" }: SkeletonProps) {
9e346cc8 return (
9e346cc9 <div
9e346cc10 className={`skeleton ${className}`}
9e346cc11 style={{
9e346cc12 width: width ?? "100%",
9e346cc13 height,
9e346cc14 minHeight: height,
9e346cc15 }}
9e346cc16 />
9e346cc17 );
9e346cc18}
9e346cc19
9e346cc20const FILE_WIDTHS = [120, 90, 140, 80, 110];
9e346cc21
9e346cc22/** A row skeleton that mimics a file tree row. */
9e346cc23export function FileRowSkeleton({ index = 0 }: { index?: number }) {
9e346cc24 return (
9e346cc25 <div className="flex items-center gap-2 py-1.5 px-3">
9e346cc26 <Skeleton width="16px" height="16px" />
9e346cc27 <Skeleton width={`${FILE_WIDTHS[index % FILE_WIDTHS.length]}px`} height="0.875rem" />
9e346cc28 </div>
9e346cc29 );
9e346cc30}
9e346cc31
9e346cc32/** A full page skeleton for repo/code views. */
9e346cc33export function RepoSkeleton() {
9e346cc34 return (
9e346cc35 <div className="max-w-3xl mx-auto px-4 py-6">
9e346cc36 <div
9e346cc37 style={{ border: "1px solid var(--border-subtle)" }}
9e346cc38 >
e33d23739 {Array.from({ length: 6 }).map((_, i) => (
9e346cc40 <div
9e346cc41 key={i}
9e346cc42 style={{
9e346cc43 borderTop: i > 0 ? "1px solid var(--divide)" : undefined,
9e346cc44 }}
9e346cc45 >
9e346cc46 <FileRowSkeleton index={i} />
9e346cc47 </div>
9e346cc48 ))}
9e346cc49 </div>
9e346cc50 </div>
9e346cc51 );
9e346cc52}
9e346cc53
36387cc54const CODE_WIDTHS = [280, 200, 320, 160, 240, 300, 180, 260, 140, 220, 290, 170];
36387cc55
36387cc56/** A skeleton for file/blob views (code viewer). */
36387cc57export function BlobSkeleton() {
36387cc58 return (
36387cc59 <div className="px-4 py-6">
36387cc60 <div style={{ border: "1px solid var(--border-subtle)" }}>
36387cc61 <div
36387cc62 className="flex items-center gap-4 px-3 py-2"
36387cc63 style={{
36387cc64 backgroundColor: "var(--bg-inset)",
36387cc65 borderBottom: "1px solid var(--border-subtle)",
36387cc66 }}
36387cc67 >
36387cc68 <Skeleton width="40px" height="0.75rem" />
36387cc69 <Skeleton width="55px" height="0.75rem" />
36387cc70 </div>
36387cc71 <div className="py-1">
36387cc72 {Array.from({ length: 12 }).map((_, i) => (
36387cc73 <div key={i} className="flex items-center gap-4 px-3 py-0.5">
36387cc74 <Skeleton width="20px" height="0.75rem" />
36387cc75 <Skeleton width={`${CODE_WIDTHS[i % CODE_WIDTHS.length]}px`} height="0.75rem" />
36387cc76 </div>
36387cc77 ))}
36387cc78 </div>
36387cc79 </div>
36387cc80 </div>
36387cc81 );
36387cc82}
36387cc83
9e346cc84const LIST_WIDTHS = [200, 170, 230, 150];
9e346cc85const LIST_SUB_WIDTHS = [100, 80, 120, 90];
9e346cc86
9e346cc87/** A skeleton for list pages (repos, commits, MRs). */
9e346cc88export function ListSkeleton({ rows = 4 }: { rows?: number }) {
9e346cc89 return (
9e346cc90 <div
9e346cc91 style={{ border: "1px solid var(--border-subtle)" }}
9e346cc92 >
9e346cc93 {Array.from({ length: rows }).map((_, i) => (
9e346cc94 <div
9e346cc95 key={i}
9e346cc96 className="py-2.5 px-3"
9e346cc97 style={{
9e346cc98 borderTop: i > 0 ? "1px solid var(--divide)" : undefined,
9e346cc99 }}
9e346cc100 >
9e346cc101 <Skeleton width={`${LIST_WIDTHS[i % LIST_WIDTHS.length]}px`} height="0.875rem" className="mb-1.5" />
9e346cc102 <Skeleton width={`${LIST_SUB_WIDTHS[i % LIST_SUB_WIDTHS.length]}px`} height="0.65rem" />
9e346cc103 </div>
9e346cc104 ))}
9e346cc105 </div>
9e346cc106 );
9e346cc107}