enzostvs HF staff commited on
Commit
118829c
·
1 Parent(s): 6e33df9

update: add trending category + pagination to load fast the page

Browse files
components/pagination.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { SpaceProps } from "@/utils/type";
2
+ import { MAX_ITEMS_PER_PAGE } from "./spaces";
3
+
4
+ export const Pagination = ({
5
+ spaces,
6
+ page,
7
+ handleNextPage,
8
+ handlePrevPage,
9
+ }: {
10
+ spaces: SpaceProps[];
11
+ page: number;
12
+ handleNextPage: () => void;
13
+ handlePrevPage: () => void;
14
+ }) => {
15
+ const max_pages = Math.ceil(spaces.length / MAX_ITEMS_PER_PAGE);
16
+
17
+ return (
18
+ <footer className="flex justify-between max-w-2xl mx-auto my-auto w-full p-3 sticky items-center bottom-3 bg-white dark:bg-slate-950 dark:border-slate-800 shadow-lg shadow-black/5 border border-zinc-200 rounded-full z-[9999]">
19
+ <button
20
+ disabled={page === 0}
21
+ className="flex items-center gap-2.5 text-gray-500 dark:text-gray-400 hover:bg-gray-100 rounded-full px-3 py-1.5 dark:hover:bg-slate-900 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:!bg-transparent"
22
+ onClick={handlePrevPage}
23
+ >
24
+ <span className="max-lg:hidden">←</span>
25
+ <span>Previous</span>
26
+ </button>
27
+ <p className="text-gray-500/70">
28
+ Page {page + 1} of {max_pages}
29
+ </p>
30
+ <button
31
+ disabled={page === max_pages - 1}
32
+ className="flex items-center gap-2.5 text-gray-500 dark:text-gray-400 hover:bg-gray-100 rounded-full px-3 py-1.5 dark:hover:bg-slate-900 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:!bg-transparent"
33
+ onClick={handleNextPage}
34
+ >
35
+ <span>Next</span>
36
+ <span className="max-lg:hidden">→</span>
37
+ </button>
38
+ </footer>
39
+ );
40
+ };
components/sort.tsx CHANGED
@@ -3,12 +3,25 @@ import { TiHeartFullOutline } from "react-icons/ti";
3
  import { MdCalendarToday } from "react-icons/md";
4
 
5
  interface Props {
6
- value: "likes" | "createdAt";
7
- onChange: (s: "createdAt" | "likes") => void;
8
  }
9
  export const Sort = ({ value, onChange }: Props) => {
10
  return (
11
  <div className="flex items-center justify-center border-[3px] rounded-full border-gray-50 drop-shadow-sm bg-gray-100 dark:bg-slate-900 dark:border-slate-800 ring-[1px] ring-gray-200 dark:ring-slate-700 gap-1">
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  <button
13
  className={classNames(
14
  "rounded-full pl-3 pr-4 py-2.5 transition-all duration-200 font-semibold text-xs hover:bg-gray-200/70 dark:hover:bg-gray-700/70 flex items-center justify-center gap-2",
 
3
  import { MdCalendarToday } from "react-icons/md";
4
 
5
  interface Props {
6
+ value: "likes" | "createdAt" | "trending";
7
+ onChange: (s: "createdAt" | "likes" | "trending") => void;
8
  }
9
  export const Sort = ({ value, onChange }: Props) => {
10
  return (
11
  <div className="flex items-center justify-center border-[3px] rounded-full border-gray-50 drop-shadow-sm bg-gray-100 dark:bg-slate-900 dark:border-slate-800 ring-[1px] ring-gray-200 dark:ring-slate-700 gap-1">
12
+ <button
13
+ className={classNames(
14
+ "rounded-full pl-3 pr-4 py-2.5 transition-all duration-200 font-semibold text-xs hover:bg-gray-200/70 dark:hover:bg-gray-700/70 flex items-center justify-center gap-2",
15
+ {
16
+ "bg-black hover:!bg-black dark:bg-slate-950 dark:hover:!bg-slate-950 text-white":
17
+ value === "trending",
18
+ }
19
+ )}
20
+ onClick={() => onChange("trending")}
21
+ >
22
+ <TiHeartFullOutline className="w-3.5 h-3.5" />
23
+ Trending
24
+ </button>
25
  <button
26
  className={classNames(
27
  "rounded-full pl-3 pr-4 py-2.5 transition-all duration-200 font-semibold text-xs hover:bg-gray-200/70 dark:hover:bg-gray-700/70 flex items-center justify-center gap-2",
components/spaces/index.tsx CHANGED
@@ -8,15 +8,22 @@ import { SpaceProps } from "@/utils/type";
8
  import { SpaceIcon } from "@/components/space_icon";
9
  import { Space } from "@/components/spaces/space";
10
  import { Sort } from "@/components/sort";
 
 
 
 
11
 
12
  export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
13
- const { systemTheme, theme, setTheme } = useTheme();
14
- const currentTheme = theme === "system" ? systemTheme : theme;
15
 
16
  const [selectedGpu, setSelectedGpu] = useState<string | undefined>(undefined);
17
  const router = useRouter();
18
- const [sort, setSort] = useState<"likes" | "createdAt">("likes");
 
 
19
  const [searchQuery, setSearchQuery] = useState<string>("");
 
 
20
 
21
  useEffect(() => {
22
  router.push("/?sort=" + sort);
@@ -30,21 +37,33 @@ export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
30
  setSelectedGpu(gpu);
31
  };
32
 
33
- const filteredSpaces = useMemo(() => {
34
- if (!searchQuery) return spaces;
35
- return spaces.filter(
36
- (space) =>
37
- space.title?.toLowerCase().includes(searchQuery.trim().toLowerCase()) ||
38
- space.authorData?.name
39
- ?.toLowerCase()
40
- .includes(searchQuery.trim().toLowerCase())
 
 
 
41
  );
42
  }, [spaces, searchQuery]);
43
 
 
 
 
 
 
 
 
 
 
44
  return (
45
- <section className="flex h-screen overflow-auto">
46
  <div className="w-full container px-6 py-10 lg:py-20 mx-auto space-y-10 lg:space-y-14">
47
- <header className="max-w-5xl mx-auto w-full max-lg:flex-col items-start flex lg:items-end lg:justify-between gap-4">
48
  <div>
49
  <div className="mb-6 flex items-center justify-start gap-2.5">
50
  <button
@@ -61,7 +80,7 @@ export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
61
  </button>
62
  <div className="font-regular text-xs text-center max-w-max rounded-full border-gray-200 bg-gray-50 border text-gray-700 dark:text-slate-400 dark:bg-slate-900 dark:border-slate-800 px-3 py-2 transition-all duration-300">
63
  <SpaceIcon className="inline-block w-4 h-4 mr-2 drop-shadow-lg" />
64
- Browse {filteredSpaces.length} spaces
65
  </div>
66
  </div>
67
  <h1 className="font-extrabold text-3xl text-black dark:text-slate-100">
@@ -71,7 +90,7 @@ export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
71
  Discover spaces with zero GPU usage on 🤗 Hugging Face Spaces.
72
  </p>
73
  </div>
74
- <div className="flex items-center justify-end gap-2">
75
  <div className="relative">
76
  <MdSearch className="absolute left-2.5 top-2.5 w-5 h-5 text-gray-600 dark:text-slate-400 z-[1]" />
77
  <input
@@ -85,7 +104,7 @@ export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
85
  <Sort value={sort} onChange={setSort} />
86
  </div>
87
  </header>
88
- <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pb-10">
89
  {filteredSpaces.length === 0 && (
90
  <div className="col-span-full flex items-center flex-col justify-center gap-2">
91
  <p className="text-neutral-500 font-regular text-base dark:text-slate-300">
@@ -96,6 +115,7 @@ export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
96
  onClick={() => {
97
  setSearchQuery("");
98
  setSelectedGpu(undefined);
 
99
  }}
100
  >
101
  Reset filters
@@ -111,6 +131,12 @@ export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
111
  />
112
  ))}
113
  </div>
 
 
 
 
 
 
114
  </div>
115
  </section>
116
  );
 
8
  import { SpaceIcon } from "@/components/space_icon";
9
  import { Space } from "@/components/spaces/space";
10
  import { Sort } from "@/components/sort";
11
+ import { Pagination } from "@/components/pagination";
12
+ import { useUpdateEffect } from "react-use";
13
+
14
+ export const MAX_ITEMS_PER_PAGE = 60;
15
 
16
  export const Spaces: React.FC<{ spaces: SpaceProps[] }> = ({ spaces }) => {
17
+ const { theme, setTheme } = useTheme();
 
18
 
19
  const [selectedGpu, setSelectedGpu] = useState<string | undefined>(undefined);
20
  const router = useRouter();
21
+ const [sort, setSort] = useState<"likes" | "createdAt" | "trending">(
22
+ "trending"
23
+ );
24
  const [searchQuery, setSearchQuery] = useState<string>("");
25
+ const [page, setPage] = useState<number>(0);
26
+ const [filteredSpaces, setFilteredSpaces] = useState<SpaceProps[]>([]);
27
 
28
  useEffect(() => {
29
  router.push("/?sort=" + sort);
 
37
  setSelectedGpu(gpu);
38
  };
39
 
40
+ useUpdateEffect(() => {
41
+ return setFilteredSpaces(
42
+ spaces.filter(
43
+ (space) =>
44
+ space.title
45
+ ?.toLowerCase()
46
+ .includes(searchQuery.trim().toLowerCase()) ||
47
+ space.authorData?.name
48
+ ?.toLowerCase()
49
+ .includes(searchQuery.trim().toLowerCase())
50
+ )
51
  );
52
  }, [spaces, searchQuery]);
53
 
54
+ useUpdateEffect(() => {
55
+ const app = document.getElementById("app");
56
+ app?.scrollTo({ top: 0, behavior: "smooth" });
57
+
58
+ const start = page * MAX_ITEMS_PER_PAGE;
59
+ const end = start + MAX_ITEMS_PER_PAGE;
60
+ setFilteredSpaces(spaces.slice(start, end));
61
+ }, [page, spaces]);
62
+
63
  return (
64
+ <section id="app" className="flex h-screen overflow-auto">
65
  <div className="w-full container px-6 py-10 lg:py-20 mx-auto space-y-10 lg:space-y-14">
66
+ <header className="max-w-5xl mx-auto w-full flex-col items-start flex gap-5 lg:gap-10">
67
  <div>
68
  <div className="mb-6 flex items-center justify-start gap-2.5">
69
  <button
 
80
  </button>
81
  <div className="font-regular text-xs text-center max-w-max rounded-full border-gray-200 bg-gray-50 border text-gray-700 dark:text-slate-400 dark:bg-slate-900 dark:border-slate-800 px-3 py-2 transition-all duration-300">
82
  <SpaceIcon className="inline-block w-4 h-4 mr-2 drop-shadow-lg" />
83
+ Browse {spaces.length} spaces
84
  </div>
85
  </div>
86
  <h1 className="font-extrabold text-3xl text-black dark:text-slate-100">
 
90
  Discover spaces with zero GPU usage on 🤗 Hugging Face Spaces.
91
  </p>
92
  </div>
93
+ <div className="flex items-center justify-between gap-2 max-lg:flex-col max-lg:items-start max-lg:gap-3 w-full">
94
  <div className="relative">
95
  <MdSearch className="absolute left-2.5 top-2.5 w-5 h-5 text-gray-600 dark:text-slate-400 z-[1]" />
96
  <input
 
104
  <Sort value={sort} onChange={setSort} />
105
  </div>
106
  </header>
107
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
108
  {filteredSpaces.length === 0 && (
109
  <div className="col-span-full flex items-center flex-col justify-center gap-2">
110
  <p className="text-neutral-500 font-regular text-base dark:text-slate-300">
 
115
  onClick={() => {
116
  setSearchQuery("");
117
  setSelectedGpu(undefined);
118
+ setPage(0);
119
  }}
120
  >
121
  Reset filters
 
131
  />
132
  ))}
133
  </div>
134
+ <Pagination
135
+ spaces={spaces}
136
+ page={page}
137
+ handleNextPage={() => setPage(page + 1)}
138
+ handlePrevPage={() => setPage(page - 1)}
139
+ />
140
  </div>
141
  </section>
142
  );
utils/index.ts CHANGED
@@ -13,7 +13,7 @@ export const fetchAllPages = async (sort = "trending") => {
13
 
14
  const jsonResponses = await Promise.all(urls);
15
  const spaces = jsonResponses.flat();
16
- return spaces;
17
  };
18
 
19
  export const sortByCreatedAt = (spaces: SpaceProps[]) => {
 
13
 
14
  const jsonResponses = await Promise.all(urls);
15
  const spaces = jsonResponses.flat();
16
+ return spaces
17
  };
18
 
19
  export const sortByCreatedAt = (spaces: SpaceProps[]) => {