返回 presentation-ai
FontList.tsx
根目录 / src / components / ui / font-picker / components / FontList.tsx
1 import { Input } from "@/components/ui/input";
2 import { ScrollArea } from "@/components/ui/scroll-area";
3 import { Skeleton } from "@/components/ui/skeleton";
4 import { Loader2, Search } from "lucide-react";
5 import dynamic from "next/dynamic";
6 import { useEffect } from "react";
7 import { useInView } from "react-intersection-observer";
8 import { type Font } from "../types";
9 import { usePaginatedFonts } from "../utils/usePaginatedFonts";
10 import { FontListItem } from "./FontListItem";
11
12 const FontPreviews = dynamic(() => import("./FontPreviews"), {
13 loading: () => <Skeleton className="h-8 w-full" />,
14 ssr: false,
15 });
16
17 export function FontList({
18 fonts,
19 currentFont,
20 onFontSelect,
21 searchValue,
22 onSearchChange,
23 isLoadingFonts,
24 }: {
25 fonts: Font[];
26 currentFont: Font;
27 onFontSelect: (font: Font) => void;
28 searchValue: string;
29 onSearchChange: (value: string) => void;
30 isLoadingFonts: boolean;
31 }) {
32 const { visibleFonts, hasMore, isLoadingMore, loadMore } = usePaginatedFonts(
33 fonts,
34 searchValue,
35 );
36
37 const { ref: loadMoreRef, inView } = useInView({
38 threshold: 0.1,
39 triggerOnce: false,
40 });
41
42 useEffect(() => {
43 if (inView && hasMore && !isLoadingMore) {
44 loadMore();
45 }
46 }, [inView, hasMore, isLoadingMore, loadMore]);
47
48 if (isLoadingFonts) {
49 return (
50 <div className="w-full space-y-2">
51 <div className="relative">
52 <Search className="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
53 <Input placeholder="Loading fonts..." disabled className="pl-8" />
54 </div>
55 <div className="flex h-96 w-full items-center justify-center rounded-md border">
56 <div className="flex items-center">
57 <Loader2 className="mr-2 h-4 w-4 animate-spin" />
58 <span>Loading fonts...</span>
59 </div>
60 </div>
61 </div>
62 );
63 }
64
65 return (
66 <div className="w-full space-y-2">
67 <div className="relative">
68 <Search className="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
69 <Input
70 placeholder="Search fonts..."
71 value={searchValue}
72 onChange={(e) => onSearchChange(e.target.value)}
73 className="pl-8"
74 />
75 </div>
76 <ScrollArea className="h-96 w-full rounded-md border">
77 <div className="space-y-1 p-2">
78 <FontPreviews>
79 {visibleFonts.map((font, index) => (
80 <FontListItem
81 key={font.sane}
82 font={font}
83 fontIndex={index}
84 isCurrent={currentFont.name === font.name}
85 onSelect={() => onFontSelect(font)}
86 />
87 ))}
88 {hasMore && (
89 <div ref={loadMoreRef} className="p-2">
90 {isLoadingMore ? (
91 <div className="flex items-center justify-center py-2">
92 <Loader2 className="mr-2 h-3 w-3 animate-spin" />
93 <span className="text-xs text-muted-foreground">
94 Loading more fonts...
95 </span>
96 </div>
97 ) : (
98 <div className="py-2 text-center text-xs text-muted-foreground">
99 Scroll for more fonts...
100 </div>
101 )}
102 </div>
103 )}
104 </FontPreviews>
105 {visibleFonts.length === 0 && !hasMore && (
106 <div className="py-4 text-center text-muted-foreground">
107 No fonts found
108 </div>
109 )}
110 </div>
111 </ScrollArea>
112 </div>
113 );
114 }
115
115 lines Plain Text