diff --git a/.env b/.env new file mode 100644 index 0000000..02eaebe --- /dev/null +++ b/.env @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://static.etherpad.org diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..afb3e74 --- /dev/null +++ b/.env.local @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL= diff --git a/app/index.css b/app/index.css index a033c8b..bfa65af 100644 --- a/app/index.css +++ b/app/index.css @@ -316,7 +316,7 @@ div#footer img.logo { } .readme-of-plugin code, .readme-of-plugin pre { - @apply text-sm p-1 rounded-sm dark:bg-gray-700 bg-gray-200 overflow-hidden; + @apply text-sm p-1 rounded-sm bg-gray-700 overflow-hidden; } .readme-of-plugin { @@ -330,3 +330,13 @@ div#footer img.logo { #banner { @apply sticky bottom-0 } + +@layer base { + :root { + --radius: 0.5rem; + } +} + +[aria-current="page"] { + @apply bg-primary bg-none; +} diff --git a/app/plugins/page.tsx b/app/plugins/page.tsx new file mode 100644 index 0000000..564a621 --- /dev/null +++ b/app/plugins/page.tsx @@ -0,0 +1,207 @@ +'use client' + +import {useUIStore} from "../../src/store/store"; +import {useEffect, useMemo, useState} from "react"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faSearch} from "@fortawesome/free-solid-svg-icons"; +import {PluginCom} from "../../src/components/Plugin"; +import axios, {AxiosResponse} from "axios"; +import {PluginMappedResponseVal, PluginResponse, ServerStats} from "../../src/store/Plugin"; +import Link from "next/link"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "@/components/ui/select" +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination" +import { Checkbox } from "@/components/ui/checkbox" + +const IMAGE_REGEX = /\b(https?:\/\/[\S]+?(?:png|jpe?g|gif))\b/; + + +export default function PluginViewer() { + const setPlugin = useUIStore(state => state.setPlugins) + const plugins = useUIStore(state => state.plugins) + const pluginSearchTerm = useUIStore(state => state.pluginSearchTerm) + const setPluginSearchTerm = useUIStore(state => state.setPluginSearchTerm) + const [officalOnly, setOfficalOnly] = useState(false) + const totalDownloads = useMemo(()=>{ + if (!plugins) return 0 + return Object.values(plugins).reduce((acc, val) => acc + val.downloads, 0) + },[plugins]) + + const [sortKey, setSortKey] = useState('newest') + const [downloadPercentage, setDownloadAveragePercentage] = useState(0) + const totalCount = useMemo(()=>{ + if (!plugins) return 0 + return Object.keys(plugins).length + }, [plugins]) + const filteredPlugins = useMemo(()=>{ + if (!plugins) return plugins + let highestDownload = 0 + + const entry: PluginMappedResponseVal[] = Object.entries(plugins).filter((plugin) => { + if (officalOnly && plugin[1].official == false) { + return false + } + if (plugin[1].keywords && pluginSearchTerm) { + for (let i = 0; i < plugin[1].keywords.length; i++) { + let keyword = plugin[1].keywords[i]; + if (keyword.toUpperCase().indexOf(pluginSearchTerm) > -1) { + return true; + } + } + } + + if (plugin[1].downloads > highestDownload) { + highestDownload = plugin[1].downloads + } + + return true + }).map(plugin=> { + return { + ...plugin[1], + name: plugin[0], + image: plugin[1].readme.match(IMAGE_REGEX)?.[0] + } satisfies PluginMappedResponseVal + }) + + setDownloadAveragePercentage(highestDownload) + entry.sort(function (a, b) { + if (sortKey === 'newest') { + if (a.time === undefined) { + return 1; + } else if (b.time === undefined) { + return -1; + } + return a.time < b.time ? 1 : -1; + } else if (sortKey === 'updated') { + if (a.modified === undefined) { + return 1; + } else if (b.modified === undefined) { + return -1; + } + return a.modified < b.modified ? 1 : -1; + } else { + return a.downloads < b.downloads ? 1 : -1; + }}) + + const chunkSize = 30; + const chunkedArray = [] + for (let i = 0; i < entry.length; i += chunkSize) { + const chunk = entry.slice(i, i + chunkSize); + chunkedArray.push(chunk) + } + + + return chunkedArray + }, [plugins, officalOnly, pluginSearchTerm]) + const [currentPage, setCurrentPage] = useState(0) + const pluginsToDisplay = useMemo(()=>{ + if (!filteredPlugins) return [] + return filteredPlugins[currentPage] + }, [currentPage, filteredPlugins]) + + + function performSearch() { + axios.get(process.env!.NEXT_PUBLIC_API_URL!+'/plugins.viewer.json') + .then((data: AxiosResponse) => { + setPlugin(data.data) + }) + } + + function performStatSearch() { + axios.get(process.env!.NEXT_PUBLIC_API_URL!+'/server-stats.json') + .then((data: AxiosResponse)=>{ + useUIStore.setState({serverStats:data.data}) + }) + } + + useEffect(() => { + performSearch(); + performStatSearch(); + }, []); + + return ( +
+
+

PluginViewer

+
+ This page lists all available plugins for etherpad hosted on npm.
{totalDownloads} downloads + of {totalCount} plugins in the last month.
+ For more information about Etherpad visit https://etherpad.org +
+

Plugins ({totalCount})

+
+ + setOfficalOnly(!officalOnly)} id="c1"/> + + + Sort by: + +
+
+ setPluginSearchTerm(v.target.value)}/> + +
+
+ { + pluginsToDisplay.map((plugin, i) => { + return + }) + } + + + + { + if (currentPage === 0) return + setCurrentPage(currentPage-1) + }} /> + + { + filteredPlugins && filteredPlugins.map((_, i) => { + return + { + setCurrentPage(i) + }}>{i+1} + + }) + } + + { + if (currentPage === filteredPlugins!.length -1) return + setCurrentPage(currentPage+1) + }} /> + + + +
+
+
+ ) +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..ea67b18 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/index.css", + "baseColor": "neutral", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs deleted file mode 100644 index 57fc45c..0000000 --- a/next.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - output: 'export', // Outputs a Single-Page Application (SPA). - distDir: './dist', // Changes the build output directory to `./dist/`. -} - -export default nextConfig diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..85d369d --- /dev/null +++ b/next.config.ts @@ -0,0 +1,19 @@ +/** @type {import('next').NextConfig} */ +const nextConfig: import('next').NextConfig = { + output: 'export', // Outputs a Single-Page Application (SPA). + distDir: './dist', // Changes the build output directory to `./dist/`. + rewrites: async () => { + return [ + { + source: '/plugins.viewer.json', + destination: 'https://static.etherpad.org/plugins.viewer.json', + }, + { + source: '/server-stats.json', + destination: 'https://static.etherpad.org/server-stats.json', + } + ] + } +} + +export default nextConfig diff --git a/package.json b/package.json index 06d5280..8eb1ba2 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,21 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-slot": "^1.1.0", "axios": "^1.7.7", + "class-variance-authority": "^0.7.0", "javascript-time-ago": "^2.5.11", - "next": "^15.0.3", + "lucide-react": "^0.456.0", "marked": "^15.0.0", + "next": "^15.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-ga": "^3.3.1", "react-waypoint": "^10.3.0", "sanitize-html": "^2.13.1", + "tailwindcss-animate": "^1.0.7", "timeago": "^1.6.7" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47dd37e..315d72b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,12 +26,27 @@ importers: '@radix-ui/react-dialog': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.2 + version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': + specifier: ^2.1.2 + version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.12)(react@18.3.1) axios: specifier: ^1.7.7 version: 1.7.7 + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 javascript-time-ago: specifier: ^2.5.11 version: 2.5.11 + lucide-react: + specifier: ^0.456.0 + version: 0.456.0(react@18.3.1) marked: specifier: ^15.0.0 version: 15.0.0 @@ -53,6 +68,9 @@ importers: sanitize-html: specifier: ^2.13.1 version: 2.13.1 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.14) timeago: specifier: ^1.6.7 version: 1.6.7 @@ -303,6 +321,21 @@ packages: resolution: {integrity: sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@fortawesome/fontawesome-common-types@6.6.0': resolution: {integrity: sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==} engines: {node: '>=6'} @@ -539,9 +572,25 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/number@1.1.0': + resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} + '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + '@radix-ui/react-arrow@1.1.0': + resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-checkbox@1.1.2': resolution: {integrity: sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==} peerDependencies: @@ -555,6 +604,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.0': + resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.0': resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} peerDependencies: @@ -564,6 +626,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.0': + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.1': resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} peerDependencies: @@ -586,6 +657,15 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-direction@1.1.0': + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.1.1': resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} peerDependencies: @@ -599,6 +679,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.2': + resolution: {integrity: sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: @@ -630,6 +723,32 @@ packages: '@types/react': optional: true + '@radix-ui/react-menu@2.1.2': + resolution: {integrity: sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.0': + resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.2': resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} peerDependencies: @@ -669,6 +788,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.0': + resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.1.2': + resolution: {integrity: sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.1.0': resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} peerDependencies: @@ -723,6 +868,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-size@1.1.0': resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} peerDependencies: @@ -732,6 +886,22 @@ packages: '@types/react': optional: true + '@radix-ui/react-visually-hidden@1.1.0': + resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@rollup/rollup-android-arm-eabi@4.25.0': resolution: {integrity: sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==} cpu: [arm] @@ -1093,9 +1263,16 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1475,6 +1652,11 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lucide-react@0.456.0: + resolution: {integrity: sha512-DIIGJqTT5X05sbAsQ+OhA8OtJYyD4NsEMCA/HQW/Y6ToPQ7gwbtujIoeAaup4HpHzV35SQOarKAWH8LYglB6eA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + marked@15.0.0: resolution: {integrity: sha512-0mouKmBROJv/WSHJBPZZyYofUgawMChnD5je/g+aOBXsHDjb/IsnTQj7mnhQZu+qPJmRQ0ecX3mLGEUm3BgwYA==} engines: {node: '>= 18'} @@ -1851,6 +2033,11 @@ packages: tailwind-merge@2.5.4: resolution: {integrity: sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==} + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@3.4.14: resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} @@ -2127,6 +2314,23 @@ snapshots: dependencies: levn: 0.4.1 + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.12 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.8': {} + '@fortawesome/fontawesome-common-types@6.6.0': {} '@fortawesome/fontawesome-svg-core@6.6.0': @@ -2302,8 +2506,19 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/number@1.1.0': {} + '@radix-ui/primitive@1.1.0': {} + '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-checkbox@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -2320,12 +2535,30 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-context@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-context@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 @@ -2354,6 +2587,12 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-direction@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -2367,6 +2606,21 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-dropdown-menu@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.12)(react@18.3.1)': dependencies: react: 18.3.1 @@ -2391,6 +2645,50 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-menu@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/rect': 1.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2420,6 +2718,52 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/react-select@2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.12)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-slot@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -2459,6 +2803,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.12)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -2466,6 +2817,17 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-visually-hidden@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + '@radix-ui/rect@1.1.0': {} + '@rollup/rollup-android-arm-eabi@4.25.0': optional: true @@ -2804,8 +3166,14 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + class-variance-authority@0.7.0: + dependencies: + clsx: 2.0.0 + client-only@0.0.1: {} + clsx@2.0.0: {} + clsx@2.1.1: {} color-convert@2.0.1: @@ -3185,6 +3553,10 @@ snapshots: lru-cache@10.4.3: {} + lucide-react@0.456.0(react@18.3.1): + dependencies: + react: 18.3.1 + marked@15.0.0: {} merge2@1.4.1: {} @@ -3567,6 +3939,10 @@ snapshots: tailwind-merge@2.5.4: {} + tailwindcss-animate@1.0.7(tailwindcss@3.4.14): + dependencies: + tailwindcss: 3.4.14 + tailwindcss@3.4.14: dependencies: '@alloc/quick-lru': 5.2.0 diff --git a/src/components/Plugin.tsx b/src/components/Plugin.tsx index f5b5771..8d6be77 100644 --- a/src/components/Plugin.tsx +++ b/src/components/Plugin.tsx @@ -1,17 +1,15 @@ -import {PluginMetaData, Plugin} from "../store/Plugin.ts"; -import {FC} from "react"; +import {PluginResponseVal} from "../store/Plugin.ts"; +import {FC, useMemo} from "react"; import TimeAgo from 'javascript-time-ago' import en from 'javascript-time-ago/locale/en' import sanitizeHtml from 'sanitize-html' import * as marked from 'marked' import {PluginAuthorComp} from "./PluginAuthorComp.tsx"; import {Chip} from "./Chip.tsx"; -import {Waypoint} from "react-waypoint"; -import {useUIStore} from "../store/store.ts"; type PluginProps = { - metadata: PluginMetaData, - plugins: Plugin, - index: number + plugins: PluginResponseVal, + index: number, + averageDownload: number } TimeAgo.addDefaultLocale(en) const timeago = new TimeAgo('en-US') @@ -21,27 +19,17 @@ const formatTime = (isoDate: string) => { return timeago.format(new Date(isoDate)) } - - -export const PluginCom: FC = ({plugins, index}) => { - const pluginResp = useUIStore(state => state.plugins) - //const setPlugins = useUIStore(state=>state.setPlugins) +export const PluginCom: FC = ({plugins, averageDownload}) => { const renderMarkdown = (text: string) => { const unsafeHtml = marked.parse(text) as string const sanitizedHtml = sanitizeHtml(unsafeHtml) return {__html: sanitizedHtml} } + const popularityScore = useMemo(() => (plugins.downloads / (averageDownload)), [plugins.downloads, averageDownload]) - const loadNextPage = () => { - /* fetch('/api/plugins') - .then(response => response.json()) - .then((data: PluginResponse) => setPlugins(data))*/ - } - - return
+ return
- {pluginResp && pluginResp?.plugins.length - 2 === index && loadNextPage()}/>}
{plugins.name} {plugins.version} @@ -50,8 +38,8 @@ export const PluginCom: FC = ({plugins, index}) => { {plugins.time &&
{formatTime(plugins.time)}
}
-
+
@@ -64,15 +52,18 @@ export const PluginCom: FC = ({plugins, index}) => { dangerouslySetInnerHTML={renderMarkdown(plugins.readme)}>
}
- + License: {plugins.license ? plugins.license : '--'} - +
+
+
+
{ - plugins.keywords.trim().length > 0 && plugins.keywords.split(",").map((k, i) => {k}) } - +
} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..be0dce8 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-neutral-300", + { + variants: { + variant: { + default: + "bg-neutral-900 text-neutral-50 shadow hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", + destructive: + "bg-red-500 text-neutral-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", + outline: + "border border-neutral-200 bg-white shadow-sm hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + secondary: + "bg-neutral-100 text-neutral-900 shadow-sm hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", + ghost: "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..0c5facc --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..4ed1dcb --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,199 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + svg]:size-4 [&>svg]:shrink-0 dark:focus:bg-neutral-800 dark:focus:text-neutral-50", + inset && "pl-8", + className + )} + {...props} + /> +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx new file mode 100644 index 0000000..d331105 --- /dev/null +++ b/src/components/ui/pagination.tsx @@ -0,0 +1,117 @@ +import * as React from "react" +import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" +import { ButtonProps, buttonVariants } from "@/components/ui/button" + +const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( +