refactor(web): Remove unused components and hooks

This commit is contained in:
Bjorn Lammers 2025-04-24 14:59:59 +02:00
parent 3e2709e7a8
commit 4946e9de55
9 changed files with 124 additions and 658 deletions

View File

@ -42,7 +42,6 @@
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.7.3",

249
web/pnpm-lock.yaml generated
View File

@ -101,9 +101,6 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
cmdk:
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
date-fns:
specifier: ^4.1.0
version: 4.1.0
@ -160,7 +157,7 @@ importers:
version: 3.2.0
tailwindcss-motion:
specifier: ^1.1.0
version: 1.1.0(tailwindcss@4.1.3)
version: 1.1.0(tailwindcss@4.1.4)
tw-animate-css:
specifier: ^1.2.5
version: 1.2.5
@ -176,13 +173,13 @@ importers:
version: 1.9.4
'@tailwindcss/postcss':
specifier: ^4.1.3
version: 4.1.3
version: 4.1.4
'@types/canvas-confetti':
specifier: ^1.9.0
version: 1.9.0
'@types/node':
specifier: ^22.14.0
version: 22.14.0
version: 22.14.1
'@types/react':
specifier: ^19.1.0
version: 19.1.0
@ -191,13 +188,13 @@ importers:
version: 19.1.2(@types/react@19.1.0)
tailwindcss:
specifier: ^4.1.3
version: 4.1.3
version: 4.1.4
typescript:
specifier: ^5.8.3
version: 5.8.3
wrangler:
specifier: ^4.12.0
version: 4.12.0
version: 4.12.1
packages:
@ -275,32 +272,32 @@ packages:
workerd:
optional: true
'@cloudflare/workerd-darwin-64@1.20250416.0':
resolution: {integrity: sha512-aZgF8Swp9eVYxJPWOoZbAgAaYjWuYqGmEA+QJ2ecRGDBqm87rT4GEw7/mmLpxrpllny3VfEEhkk9iYCGv8nlFw==}
'@cloudflare/workerd-darwin-64@1.20250417.0':
resolution: {integrity: sha512-4Adfl92aKepjxb8e6af2d+xpD2sBOADgHqvkyXsFmoLb80weMEDDRGJi1p1m5q1M78/oVnGcpdmuRCAathanRg==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@cloudflare/workerd-darwin-arm64@1.20250416.0':
resolution: {integrity: sha512-FhswG1QYRfaTZ4FAlUkfVWaoM2lrlqumiBTrhbo9czMJdGR/oBXS4SGynuI6zyhApHeBf3/fZpA/SBAe4cXdgg==}
'@cloudflare/workerd-darwin-arm64@1.20250417.0':
resolution: {integrity: sha512-dSlk18F4i3T1OTzFBxx3pKpXRMP6w2xZ26+oIV32BFWrCi/HxGzUd6gVA0q37oLGqITRt8xU693J4Gl1CwC/Ag==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@cloudflare/workerd-linux-64@1.20250416.0':
resolution: {integrity: sha512-G+nXEAJ/9y+A857XShwxKeRdfxok6UcjiQe6G+wQeCn/Ofkp/EWydacKdyeVU6QIm1oHS78DwJ7AzbCYywf9aw==}
'@cloudflare/workerd-linux-64@1.20250417.0':
resolution: {integrity: sha512-27MVzOa/lENcqewC2L9EcqstXW843UhjBMcwV1umDfsjwLyZOEv6Gtm/6j5r0L0gASvkRTam3fAmtPk/gt48TA==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@cloudflare/workerd-linux-arm64@1.20250416.0':
resolution: {integrity: sha512-U6oVW0d9w1fpnDYNrjPJ9SFkDlGJWJWbXHlTBObXl6vccP16WewvuxyHkKqyUhUc8hyBaph7sxeKzKmuCFQ4SA==}
'@cloudflare/workerd-linux-arm64@1.20250417.0':
resolution: {integrity: sha512-34qBk0htAXmUneOTQxW6/g6pjNVR91r0vJzz2FID84cAIOYVl4hZLijkjmVl+MMDU6boXUs+yDwhItdg06YvAg==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@cloudflare/workerd-windows-64@1.20250416.0':
resolution: {integrity: sha512-YAjjTzL1z9YYeN4sqYfj1dtQXd2Bblj+B+hl4Rz2aOhblpZEZAdhapZlOCRvLLkOJshKJUnRD3mDlytAdgwybQ==}
'@cloudflare/workerd-windows-64@1.20250417.0':
resolution: {integrity: sha512-PDwATFioff+geVHfgTzSWsxgwjgotrdXStb0EL0lMyMT5zNmHArAnOx83CbDtud63Uv9rVX1BAfPP4tyD1O+5A==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
@ -1365,81 +1362,93 @@ packages:
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@tailwindcss/node@4.1.3':
resolution: {integrity: sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA==}
'@tailwindcss/node@4.1.4':
resolution: {integrity: sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==}
'@tailwindcss/oxide-android-arm64@4.1.3':
resolution: {integrity: sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g==}
'@tailwindcss/oxide-android-arm64@4.1.4':
resolution: {integrity: sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.1.3':
resolution: {integrity: sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw==}
'@tailwindcss/oxide-darwin-arm64@4.1.4':
resolution: {integrity: sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.1.3':
resolution: {integrity: sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg==}
'@tailwindcss/oxide-darwin-x64@4.1.4':
resolution: {integrity: sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.1.3':
resolution: {integrity: sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w==}
'@tailwindcss/oxide-freebsd-x64@4.1.4':
resolution: {integrity: sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3':
resolution: {integrity: sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg==}
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4':
resolution: {integrity: sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.1.3':
resolution: {integrity: sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg==}
'@tailwindcss/oxide-linux-arm64-gnu@4.1.4':
resolution: {integrity: sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-arm64-musl@4.1.3':
resolution: {integrity: sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ==}
'@tailwindcss/oxide-linux-arm64-musl@4.1.4':
resolution: {integrity: sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tailwindcss/oxide-linux-x64-gnu@4.1.3':
resolution: {integrity: sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA==}
'@tailwindcss/oxide-linux-x64-gnu@4.1.4':
resolution: {integrity: sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-linux-x64-musl@4.1.3':
resolution: {integrity: sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw==}
'@tailwindcss/oxide-linux-x64-musl@4.1.4':
resolution: {integrity: sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tailwindcss/oxide-win32-arm64-msvc@4.1.3':
resolution: {integrity: sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg==}
'@tailwindcss/oxide-wasm32-wasi@4.1.4':
resolution: {integrity: sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.1.4':
resolution: {integrity: sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.1.3':
resolution: {integrity: sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA==}
'@tailwindcss/oxide-win32-x64-msvc@4.1.4':
resolution: {integrity: sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.1.3':
resolution: {integrity: sha512-t16lpHCU7LBxDe/8dCj9ntyNpXaSTAgxWm1u2XQP5NiIu4KGSyrDJJRlK9hJ4U9yJxx0UKCVI67MJWFNll5mOQ==}
'@tailwindcss/oxide@4.1.4':
resolution: {integrity: sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==}
engines: {node: '>= 10'}
'@tailwindcss/postcss@4.1.3':
resolution: {integrity: sha512-6s5nJODm98F++QT49qn8xJKHQRamhYHfMi3X7/ltxiSQ9dyRsaFSfFkfaMsanWzf+TMYQtbk8mt5f6cCVXJwfg==}
'@tailwindcss/postcss@4.1.4':
resolution: {integrity: sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw==}
'@tanstack/react-virtual@3.13.6':
resolution: {integrity: sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==}
@ -1480,8 +1489,8 @@ packages:
'@types/d3-timer@3.0.2':
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
'@types/node@22.14.0':
resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==}
'@types/node@22.14.1':
resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==}
'@types/react-dom@19.1.2':
resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==}
@ -1540,12 +1549,6 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cmdk@1.1.1:
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -1888,8 +1891,8 @@ packages:
engines: {node: '>=10.0.0'}
hasBin: true
miniflare@4.20250416.0:
resolution: {integrity: sha512-261PhPgD9zs5/BTdbWqwiaXtWxb+Av5zKCwTU+HXrA5E4tf3qnULwh3u6SVUOAEArEroFuKJzawsQ9COtNBurQ==}
miniflare@4.20250417.0:
resolution: {integrity: sha512-bROKLQKr4CoS93tnGuw5e08VaNwM3VowTL3Z2Cps1HzY6a4Bq8uNtggQ7WogriMq77jcHn6kbz64bvWyF//Jkw==}
engines: {node: '>=18.0.0'}
hasBin: true
@ -2153,8 +2156,8 @@ packages:
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
tailwindcss@4.1.3:
resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==}
tailwindcss@4.1.4:
resolution: {integrity: sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==}
tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
@ -2219,17 +2222,17 @@ packages:
web-vitals@4.2.4:
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
workerd@1.20250416.0:
resolution: {integrity: sha512-Yrx/bZAKbmSvomdTAzzIpOHwpYhs0ldr2wqed22UEhQ0mIplAHY4xmY+SjAJhP/TydZrciOVzBxwM1+4T40KNA==}
workerd@1.20250417.0:
resolution: {integrity: sha512-naz6oJiVODd3/Lkp9l3vtc56HKOOvx+AWDvEsTa5eSfi5SI9V0HYpLYSPblAwrfazbQ4ff1Vl3jkTl/5JxqCAA==}
engines: {node: '>=16'}
hasBin: true
wrangler@4.12.0:
resolution: {integrity: sha512-4rfAXOi5KqM3ECvOrZJ97k3zEqxVwtdt4bijd8jcRBZ6iJYvEtjgjVi4TsfkVa/eXGhpfHTUnKu2uk8UHa8M2w==}
wrangler@4.12.1:
resolution: {integrity: sha512-jYrz8y2ffhsRqvQLO2dXFi9HLvPUJk3jn7U71GWfBBCHm0I6r2ik7Vs9ajpRcTGlbNw1RY0uIHVJBVR/7bEN5A==}
engines: {node: '>=18.0.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20250415.0
'@cloudflare/workers-types': ^4.20250417.0
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
@ -2302,25 +2305,25 @@ snapshots:
dependencies:
mime: 3.0.0
'@cloudflare/unenv-preset@2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250416.0)':
'@cloudflare/unenv-preset@2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250417.0)':
dependencies:
unenv: 2.0.0-rc.15
optionalDependencies:
workerd: 1.20250416.0
workerd: 1.20250417.0
'@cloudflare/workerd-darwin-64@1.20250416.0':
'@cloudflare/workerd-darwin-64@1.20250417.0':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20250416.0':
'@cloudflare/workerd-darwin-arm64@1.20250417.0':
optional: true
'@cloudflare/workerd-linux-64@1.20250416.0':
'@cloudflare/workerd-linux-64@1.20250417.0':
optional: true
'@cloudflare/workerd-linux-arm64@1.20250416.0':
'@cloudflare/workerd-linux-arm64@1.20250417.0':
optional: true
'@cloudflare/workerd-windows-64@1.20250416.0':
'@cloudflare/workerd-windows-64@1.20250417.0':
optional: true
'@cspotcode/source-map-support@0.8.1':
@ -3264,67 +3267,71 @@ snapshots:
dependencies:
tslib: 2.8.1
'@tailwindcss/node@4.1.3':
'@tailwindcss/node@4.1.4':
dependencies:
enhanced-resolve: 5.18.1
jiti: 2.4.2
lightningcss: 1.29.2
tailwindcss: 4.1.3
tailwindcss: 4.1.4
'@tailwindcss/oxide-android-arm64@4.1.3':
'@tailwindcss/oxide-android-arm64@4.1.4':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.1.3':
'@tailwindcss/oxide-darwin-arm64@4.1.4':
optional: true
'@tailwindcss/oxide-darwin-x64@4.1.3':
'@tailwindcss/oxide-darwin-x64@4.1.4':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.1.3':
'@tailwindcss/oxide-freebsd-x64@4.1.4':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3':
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.4':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.1.3':
'@tailwindcss/oxide-linux-arm64-gnu@4.1.4':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.1.3':
'@tailwindcss/oxide-linux-arm64-musl@4.1.4':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.1.3':
'@tailwindcss/oxide-linux-x64-gnu@4.1.4':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.1.3':
'@tailwindcss/oxide-linux-x64-musl@4.1.4':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.1.3':
'@tailwindcss/oxide-wasm32-wasi@4.1.4':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.1.3':
'@tailwindcss/oxide-win32-arm64-msvc@4.1.4':
optional: true
'@tailwindcss/oxide@4.1.3':
'@tailwindcss/oxide-win32-x64-msvc@4.1.4':
optional: true
'@tailwindcss/oxide@4.1.4':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.1.3
'@tailwindcss/oxide-darwin-arm64': 4.1.3
'@tailwindcss/oxide-darwin-x64': 4.1.3
'@tailwindcss/oxide-freebsd-x64': 4.1.3
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.3
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.3
'@tailwindcss/oxide-linux-arm64-musl': 4.1.3
'@tailwindcss/oxide-linux-x64-gnu': 4.1.3
'@tailwindcss/oxide-linux-x64-musl': 4.1.3
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.3
'@tailwindcss/oxide-win32-x64-msvc': 4.1.3
'@tailwindcss/oxide-android-arm64': 4.1.4
'@tailwindcss/oxide-darwin-arm64': 4.1.4
'@tailwindcss/oxide-darwin-x64': 4.1.4
'@tailwindcss/oxide-freebsd-x64': 4.1.4
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.4
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.4
'@tailwindcss/oxide-linux-arm64-musl': 4.1.4
'@tailwindcss/oxide-linux-x64-gnu': 4.1.4
'@tailwindcss/oxide-linux-x64-musl': 4.1.4
'@tailwindcss/oxide-wasm32-wasi': 4.1.4
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.4
'@tailwindcss/oxide-win32-x64-msvc': 4.1.4
'@tailwindcss/postcss@4.1.3':
'@tailwindcss/postcss@4.1.4':
dependencies:
'@alloc/quick-lru': 5.2.0
'@tailwindcss/node': 4.1.3
'@tailwindcss/oxide': 4.1.3
'@tailwindcss/node': 4.1.4
'@tailwindcss/oxide': 4.1.4
postcss: 8.5.3
tailwindcss: 4.1.3
tailwindcss: 4.1.4
'@tanstack/react-virtual@3.13.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
@ -3360,7 +3367,7 @@ snapshots:
'@types/d3-timer@3.0.2': {}
'@types/node@22.14.0':
'@types/node@22.14.1':
dependencies:
undici-types: 6.21.0
@ -3417,18 +3424,6 @@ snapshots:
clsx@2.1.1: {}
cmdk@1.1.1(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.0)(react@19.1.0)
'@radix-ui/react-dialog': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-id': 1.1.1(@types/react@19.1.0)(react@19.1.0)
'@radix-ui/react-primitive': 2.0.3(@types/react-dom@19.1.2(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@ -3735,7 +3730,7 @@ snapshots:
mime@3.0.0: {}
miniflare@4.20250416.0:
miniflare@4.20250417.0:
dependencies:
'@cspotcode/source-map-support': 0.8.1
acorn: 8.14.0
@ -3744,7 +3739,7 @@ snapshots:
glob-to-regexp: 0.4.1
stoppable: 1.1.0
undici: 5.29.0
workerd: 1.20250416.0
workerd: 1.20250417.0
ws: 8.18.0
youch: 3.3.4
zod: 3.22.3
@ -4025,11 +4020,11 @@ snapshots:
tailwind-merge@3.2.0: {}
tailwindcss-motion@1.1.0(tailwindcss@4.1.3):
tailwindcss-motion@1.1.0(tailwindcss@4.1.4):
dependencies:
tailwindcss: 4.1.3
tailwindcss: 4.1.4
tailwindcss@4.1.3: {}
tailwindcss@4.1.4: {}
tapable@2.2.1: {}
@ -4100,24 +4095,24 @@ snapshots:
web-vitals@4.2.4: {}
workerd@1.20250416.0:
workerd@1.20250417.0:
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20250416.0
'@cloudflare/workerd-darwin-arm64': 1.20250416.0
'@cloudflare/workerd-linux-64': 1.20250416.0
'@cloudflare/workerd-linux-arm64': 1.20250416.0
'@cloudflare/workerd-windows-64': 1.20250416.0
'@cloudflare/workerd-darwin-64': 1.20250417.0
'@cloudflare/workerd-darwin-arm64': 1.20250417.0
'@cloudflare/workerd-linux-64': 1.20250417.0
'@cloudflare/workerd-linux-arm64': 1.20250417.0
'@cloudflare/workerd-windows-64': 1.20250417.0
wrangler@4.12.0:
wrangler@4.12.1:
dependencies:
'@cloudflare/kv-asset-handler': 0.4.0
'@cloudflare/unenv-preset': 2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250416.0)
'@cloudflare/unenv-preset': 2.3.1(unenv@2.0.0-rc.15)(workerd@1.20250417.0)
blake3-wasm: 2.1.5
esbuild: 0.25.2
miniflare: 4.20250416.0
miniflare: 4.20250417.0
path-to-regexp: 6.3.0
unenv: 2.0.0-rc.15
workerd: 1.20250416.0
workerd: 1.20250417.0
optionalDependencies:
fsevents: 2.3.3
sharp: 0.33.5

View File

@ -118,19 +118,6 @@
transform: rotate(-5deg) scale(0.9);
}
}
--animate-shiny-text: shiny-text 8s infinite;
@keyframes shiny-text {
0%,
90%,
100% {
background-position: calc(-100% - var(--shiny-width)) 0;
}
30%,
60% {
background-position: calc(100% + var(--shiny-width)) 0;
}
}
}
:root {
@ -199,7 +186,7 @@
--secondary: oklch(0.31 0.03 266.71);
--secondary-foreground: oklch(0.92 0 0);
--muted: oklch(0.31 0.03 266.71);
--muted-foreground: oklch(0.72 0 0);
--muted-foreground: oklch(0.78 0 0);
--accent: oklch(0.34 0.06 267.59);
--accent-foreground: oklch(0.88 0.06 254.13);
--destructive: oklch(0.64 0.21 25.33);

View File

@ -1,87 +0,0 @@
"use client"
import { Input } from "@/components/ui/input"
import { BASE_URL } from "@/constants"
import type { IconSearchProps, IconWithName } from "@/types/icons"
import { Search } from "lucide-react"
import Image from "next/image"
import Link from "next/link"
import { useState } from "react"
export function IconSearch({ icons, initialQuery = "" }: IconSearchProps) {
const [searchQuery, setSearchQuery] = useState(initialQuery)
const [filteredIcons, setFilteredIcons] = useState<IconWithName[]>(() => {
if (!initialQuery.trim()) return icons
const q = initialQuery.toLowerCase()
return icons.filter(({ name, data }) => {
if (name.toLowerCase().includes(q)) return true
if (data.aliases.some((alias) => alias.toLowerCase().includes(q))) return true
if (data.categories.some((category) => category.toLowerCase().includes(q))) return true
return false
})
})
const handleSearch = (query: string) => {
setSearchQuery(query)
if (!query.trim()) {
setFilteredIcons(icons)
return
}
const q = query.toLowerCase()
const filtered = icons.filter(({ name, data }) => {
if (name.toLowerCase().includes(q)) return true
if (data.aliases.some((alias) => alias.toLowerCase().includes(q))) return true
if (data.categories.some((category) => category.toLowerCase().includes(q))) return true
return false
})
setFilteredIcons(filtered)
}
return (
<>
<div className="relative w-full max-w-md">
<Search className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground transition-all duration-300" />
<Input
type="search"
placeholder="Search icons by name, aliases, or categories..."
className="w-full pl-8 transition-all duration-300 text-sm md:text-base"
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
/>
</div>
{filteredIcons.length === 0 ? (
<div className="text-center py-12">
<h2 className="text-xl font-semibold">No icons found</h2>
<p className="text-muted-foreground mt-2">Try a different search term.</p>
</div>
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4 mt-8">
{filteredIcons.map(({ name, data }) => (
<Link
key={name}
href={`/icons/${name}`}
className="group flex flex-col items-center p-4 rounded-lg border border-border hover:border-primary hover:bg-accent transition-colors"
>
<div className="relative h-16 w-16 mb-2">
<Image
src={`${BASE_URL}/${data.base}/${name}.${data.base}`}
alt={`${name} icon`}
fill
className="object-contain p-1 group-hover:scale-110 transition-transform"
/>
</div>
<span className="text-sm text-center truncate w-full">{name.replace(/-/g, " ")}</span>
</Link>
))}
</div>
)}
</>
)
}

View File

@ -1,138 +0,0 @@
"use client"
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { useMediaQuery } from "@/hooks/use-media-query"
import { fuzzySearch } from "@/lib/utils"
import { Icon } from "@/types/icons"
import { useRouter } from "next/navigation"
import { useCallback, useEffect, useState } from "react"
interface CommandMenuProps {
icons: {
name: string
data: {
categories: string[]
aliases: string[]
[key: string]: unknown
}
}[]
triggerButtonId?: string
open?: boolean
onOpenChange?: (open: boolean) => void
}
export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalOnOpenChange }: CommandMenuProps) {
const router = useRouter()
const [internalOpen, setInternalOpen] = useState(false)
const [query, setQuery] = useState("")
const isDesktop = useMediaQuery("(min-width: 768px)")
// Use either external or internal state for controlling open state
const isOpen = externalOpen !== undefined ? externalOpen : internalOpen
// Wrap setIsOpen in useCallback to fix dependency issue
const setIsOpen = useCallback(
(value: boolean) => {
if (externalOnOpenChange) {
externalOnOpenChange(value)
} else {
setInternalOpen(value)
}
},
[externalOnOpenChange],
)
const filteredIcons = getFilteredIcons(icons, query)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (
(e.key === "k" && (e.metaKey || e.ctrlKey)) ||
(e.key === "/" && document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA")
) {
e.preventDefault()
setIsOpen(!isOpen)
}
}
document.addEventListener("keydown", handleKeyDown)
return () => document.removeEventListener("keydown", handleKeyDown)
}, [isOpen, setIsOpen])
function getFilteredIcons(iconList: CommandMenuProps["icons"], query: string) {
if (!query) {
// Return a limited number of icons when no query is provided
return iconList.slice(0, 8)
}
// Calculate scores for each icon
const scoredIcons = iconList.map((icon) => {
// Calculate scores for different fields
const nameScore = fuzzySearch(icon.name, query) * 2.0 // Give more weight to name matches
// Get max score from aliases
const aliasScore =
icon.data.aliases && icon.data.aliases.length > 0
? Math.max(...icon.data.aliases.map((alias) => fuzzySearch(alias, query))) * 1.8 // Increased weight for aliases
: 0
// Get max score from categories
const categoryScore =
icon.data.categories && icon.data.categories.length > 0
? Math.max(...icon.data.categories.map((category) => fuzzySearch(category, query)))
: 0
// Use the highest score
const score = Math.max(nameScore, aliasScore, categoryScore)
return { icon, score, matchedField: score === nameScore ? "name" : score === aliasScore ? "alias" : "category" }
})
// Filter icons with a minimum score and sort by highest score
return scoredIcons
.filter((item) => item.score > 0.3) // Higher threshold for more accurate results
.sort((a, b) => b.score - a.score)
.slice(0, 20) // Limit the number of results
.map((item) => item.icon)
}
const handleSelect = (name: string) => {
setIsOpen(false)
router.push(`/icons/${name}`)
}
return (
<CommandDialog open={isOpen} onOpenChange={setIsOpen}>
<CommandInput placeholder="Search for icons by name, category, or purpose..." value={query} onValueChange={setQuery} />
<CommandList>
<CommandEmpty>No matching icons found. Try a different search term or browse all icons.</CommandEmpty>
<CommandGroup heading="Icons">
{filteredIcons.map(({ name, data }) => {
// Find matched alias for display if available
const matchedAlias =
query && data.aliases && data.aliases.length > 0
? data.aliases.find((alias) => alias.toLowerCase().includes(query.toLowerCase()))
: null
return (
<CommandItem key={name} value={name} onSelect={() => handleSelect(name)} className="flex items-center gap-2 cursor-pointer">
<div className="flex-shrink-0 h-5 w-5 relative">
<div className="h-5 w-5 bg-rose-100 dark:bg-rose-900/30 rounded-md flex items-center justify-center">
<span className="text-[10px] font-medium text-rose-800 dark:text-rose-300">{name.substring(0, 2).toUpperCase()}</span>
</div>
</div>
<span className="flex-grow capitalize">{name.replace(/-/g, " ")}</span>
{matchedAlias && <span className="text-xs text-primary-500 truncate max-w-[100px]">alias: {matchedAlias}</span>}
{!matchedAlias && data.categories && data.categories.length > 0 && (
<span className="text-xs text-muted-foreground truncate max-w-[100px]">
{data.categories[0].replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}
</span>
)}
</CommandItem>
)
})}
</CommandGroup>
</CommandList>
</CommandDialog>
)
}

View File

@ -3,42 +3,14 @@
import { IconSubmissionForm } from "@/components/icon-submission-form"
import { ThemeSwitcher } from "@/components/theme-switcher"
import { REPO_PATH } from "@/constants"
import { getIconsArray } from "@/lib/api"
import type { IconWithName } from "@/types/icons"
import { motion } from "framer-motion"
import { Github, Search } from "lucide-react"
import { Github } from "lucide-react"
import Link from "next/link"
import { useEffect, useState } from "react"
import { CommandMenu } from "./command-menu"
import { HeaderNav } from "./header-nav"
import { Button } from "./ui/button"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip"
export function Header() {
const [iconsData, setIconsData] = useState<IconWithName[]>([])
const [isLoaded, setIsLoaded] = useState(false)
const [commandMenuOpen, setCommandMenuOpen] = useState(false)
useEffect(() => {
async function loadIcons() {
try {
const icons = await getIconsArray()
setIconsData(icons)
setIsLoaded(true)
} catch (error) {
console.error("Failed to load icons:", error)
setIsLoaded(true)
}
}
loadIcons()
}, [])
// Function to open the command menu
const openCommandMenu = () => {
setCommandMenuOpen(true)
}
return (
<motion.header
className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50"
@ -56,30 +28,6 @@ export function Header() {
</div>
</div>
<div className="flex items-center gap-2 md:gap-4">
{/* Desktop search button */}
<div className="hidden md:block">
<Button variant="outline" className="gap-2 cursor-pointer transition-all duration-300" onClick={openCommandMenu}>
<Search className="h-4 w-4 transition-all duration-300" />
<span>Find icons</span>
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-border/80 bg-muted/80 px-1.5 font-mono text-[10px] font-medium opacity-100">
<span className="text-xs"></span>K
</kbd>
</Button>
</div>
{/* Mobile search button */}
<div className="md:hidden">
<Button
variant="ghost"
size="icon"
className="rounded-lg cursor-pointer transition-all duration-300 hover:ring-2 "
onClick={openCommandMenu}
>
<Search className="h-5 w-5 transition-all duration-300" />
<span className="sr-only">Find icons</span>
</Button>
</div>
<div className="hidden md:flex items-center gap-2 md:gap-4">
<IconSubmissionForm />
<TooltipProvider>
@ -106,9 +54,6 @@ export function Header() {
<ThemeSwitcher />
</div>
</div>
{/* Single instance of CommandMenu */}
{isLoaded && <CommandMenu icons={iconsData} open={commandMenuOpen} onOpenChange={setCommandMenuOpen} />}
</motion.header>
)
}

View File

@ -1,33 +0,0 @@
import type { CSSProperties, ComponentPropsWithoutRef, FC } from "react"
import { cn } from "@/lib/utils"
export interface AnimatedShinyTextProps extends ComponentPropsWithoutRef<"span"> {
shimmerWidth?: number
}
export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({ children, className, shimmerWidth = 100, ...props }) => {
return (
<span
style={
{
"--shiny-width": `${shimmerWidth}px`,
} as CSSProperties
}
className={cn(
"mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70",
// Shine effect
"animate-shiny-text bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shiny-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
// Shine gradient
"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
className,
)}
{...props}
>
{children}
</span>
)
}

View File

@ -1,177 +0,0 @@
"use client"
import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
function Command({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
className
)}
{...props}
/>
)
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
function CommandInput({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div
data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b px-3"
>
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
)
}
function CommandList({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn(
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
className
)}
{...props}
/>
)
}
function CommandEmpty({
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return (
<CommandPrimitive.Empty
data-slot="command-empty"
className="py-6 text-center text-sm"
{...props}
/>
)
}
function CommandGroup({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
className
)}
{...props}
/>
)
}
function CommandSeparator({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return (
<CommandPrimitive.Separator
data-slot="command-separator"
className={cn("bg-border -mx-1 h-px", className)}
{...props}
/>
)
}
function CommandItem({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function CommandShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="command-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@ -1,25 +0,0 @@
"use client"
import { useEffect, useState } from "react"
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false)
useEffect(() => {
const media = window.matchMedia(query)
// Initial check
if (media.matches !== matches) {
setMatches(media.matches)
}
// Setup listener for changes
const listener = () => setMatches(media.matches)
media.addEventListener("change", listener)
// Cleanup
return () => media.removeEventListener("change", listener)
}, [query, matches])
return matches
}