diff --git a/src/components/ModsList.tsx b/src/components/ModsList.tsx
new file mode 100644
index 0000000..56b5761
--- /dev/null
+++ b/src/components/ModsList.tsx
@@ -0,0 +1,161 @@
+import React, { useState, useMemo } from 'react'
+import type { ZenTheme } from '../mods'
+import { library, icon } from '@fortawesome/fontawesome-svg-core'
+import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'
+
+// Add icons to the library
+library.add(faSort, faSortUp, faSortDown)
+
+// Create icon objects
+const defaultSortIcon = icon({ prefix: 'fas', iconName: 'sort' })
+const ascSortIcon = icon({ prefix: 'fas', iconName: 'sort-up' })
+const descSortIcon = icon({ prefix: 'fas', iconName: 'sort-down' })
+
+interface ModsListProps {
+ mods: ZenTheme[]
+}
+
+export default function ModsList({ mods }: ModsListProps) {
+ const [search, setSearch] = useState('')
+ const [createdSort, setCreatedSort] = useState<'default' | 'asc' | 'desc'>(
+ 'default',
+ )
+ const [updatedSort, setUpdatedSort] = useState<'default' | 'asc' | 'desc'>(
+ 'default',
+ )
+
+ const toggleCreatedSort = () => {
+ setCreatedSort((prev) => {
+ if (prev === 'default') return 'asc'
+ if (prev === 'asc') return 'desc'
+ return 'default'
+ })
+ }
+
+ const toggleUpdatedSort = () => {
+ setUpdatedSort((prev) => {
+ if (prev === 'default') return 'asc'
+ if (prev === 'asc') return 'desc'
+ return 'default'
+ })
+ }
+
+ function getSortIcon(state: 'default' | 'asc' | 'desc') {
+ if (state === 'asc') return ascSortIcon
+ if (state === 'desc') return descSortIcon
+ return defaultSortIcon
+ }
+
+ const filteredAndSortedMods = useMemo(() => {
+ let filtered = [...mods]
+
+ // Filter by search
+ const searchTerm = search.toLowerCase()
+ if (searchTerm) {
+ filtered = filtered.filter(
+ (mod) =>
+ mod.name.toLowerCase().includes(searchTerm) ||
+ mod.description.toLowerCase().includes(searchTerm) ||
+ mod.author.toLowerCase().includes(searchTerm) ||
+ (mod.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ??
+ false),
+ )
+ }
+
+ // Sort by createdAt if chosen
+ if (createdSort !== 'default') {
+ filtered.sort((a, b) => {
+ const diff =
+ new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
+ return createdSort === 'asc' ? diff : -diff
+ })
+ }
+
+ // Sort by updatedAt if chosen
+ if (updatedSort !== 'default') {
+ filtered.sort((a, b) => {
+ const diff =
+ new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
+ return updatedSort === 'asc' ? diff : -diff
+ })
+ }
+
+ return filtered
+ }, [mods, search, createdSort, updatedSort])
+
+ return (
+
+
+
+ setSearch(e.target.value)}
+ />
+
+
+
+
+
+ Last created
+
+
+
+
+
+
+ Last updated
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/mods.ts b/src/mods.ts
index 4dec622..4cb95b6 100644
--- a/src/mods.ts
+++ b/src/mods.ts
@@ -1,35 +1,65 @@
export interface ZenTheme {
- name: string;
- description: string;
- image: string;
- downloadUrl: string;
- id: string;
- homepage?: string;
- readme: string;
- preferences?: string;
- isColorTheme: boolean;
- author: string;
- version: string;
- tags: string[];
- createdAt: Date;
- updatedAt: Date;
+ name: string
+ description: string
+ image: string
+ downloadUrl: string
+ id: string
+ homepage?: string
+ readme: string
+ preferences?: string
+ isColorTheme: boolean
+ author: string
+ version: string
+ tags: string[]
+ createdAt: Date
+ updatedAt: Date
}
-const THEME_API = "https://zen-browser.github.io/theme-store/themes.json";
+const THEME_API = 'https://zen-browser.github.io/theme-store/themes.json'
-export async function getAllMods(): Promise {
+interface FilterOptions {
+ createdAt?: Date
+ updatedAt?: Date
+ search?: string
+}
+
+export async function getAllMods(filters?: FilterOptions): Promise {
try {
- const res = await fetch(THEME_API);
- const json = await res.json();
+ const res = await fetch(THEME_API)
+ const json = await res.json()
// convert dict to array
- const mods = Object.keys(json).map((key) => json[key]);
- return mods;
+ let mods: ZenTheme[] = Object.keys(json).map((key) => json[key])
+
+ if (filters) {
+ if (filters.createdAt) {
+ mods = mods.filter(
+ (mod) => new Date(mod.createdAt) >= filters.createdAt!,
+ )
+ }
+ if (filters.updatedAt) {
+ mods = mods.filter(
+ (mod) => new Date(mod.updatedAt) >= filters.updatedAt!,
+ )
+ }
+ if (filters.search) {
+ const searchLower = filters.search.toLowerCase()
+ mods = mods.filter(
+ (mod) =>
+ mod.name.toLowerCase().includes(searchLower) ||
+ mod.description.toLowerCase().includes(searchLower) ||
+ mod.author.toLowerCase().includes(searchLower) ||
+ mod.tags.some((tag) => tag.toLowerCase().includes(searchLower)),
+ )
+ }
+ }
+
+ return mods
} catch (error) {
- console.error(error);
- return [];
+ console.error(error)
+ return []
}
}
export function getAuthorLink(author: string): string {
- return `https://github.com/${author}`;
+ return `https://github.com/${author}`
}
diff --git a/src/pages/mods/index.astro b/src/pages/mods/index.astro
index 63b160c..ab7476f 100644
--- a/src/pages/mods/index.astro
+++ b/src/pages/mods/index.astro
@@ -1,46 +1,28 @@
---
-import Description from '../../components/Description.astro';
-import Title from '../../components/Title.astro';
-import Layout from '../../layouts/Layout.astro';
-import { getAllMods, type ZenTheme } from '../../mods';
+import Description from '../../components/Description.astro'
+import Title from '../../components/Title.astro'
+import Layout from '../../layouts/Layout.astro'
+import ModsList from '../../components/ModsList'
+import { getAllMods } from '../../mods'
-const mods = await getAllMods() || [];
+const mods = (await getAllMods({})) || []
---
-