From 6ea0084e3dcee8af8f3dd6b46e93c1c4d0500779 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 18 Feb 2024 11:57:25 -0600 Subject: [PATCH 01/16] Add cmdk dependency --- .vscode/settings.json | 1 + package.json | 1 + pnpm-lock.yaml | 220 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1a06b433..ea2c7fe4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "Astia", "camelcase", "cloudflarestorage", + "cmdk", "CredentialsSignin", "Eterna", "exif", diff --git a/package.json b/package.json index 71b43d7a..6d9cdb21 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "autoprefixer": "10.4.17", "camelcase-keys": "^9.1.3", "clsx": "^2.1.0", + "cmdk": "^0.2.1", "date-fns": "^3.3.1", "eslint": "8.56.0", "eslint-config-next": "14.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ea60b70..d0d7071d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ dependencies: clsx: specifier: ^2.1.0 version: 2.1.0 + cmdk: + specifier: ^0.2.1 + version: 0.2.1(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) date-fns: specifier: ^3.3.1 version: 3.3.1 @@ -1684,6 +1687,12 @@ packages: resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} dev: false + /@radix-ui/primitive@1.0.0: + resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==} + dependencies: + '@babel/runtime': 7.23.9 + dev: false + /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: @@ -1735,6 +1744,15 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-compose-refs@1.0.0(react@18.2.0): + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + react: 18.2.0 + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -1749,6 +1767,15 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-context@1.0.0(react@18.2.0): + resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + react: 18.2.0 + dev: false + /@radix-ui/react-context@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} peerDependencies: @@ -1763,6 +1790,33 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-dialog@1.0.0(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-context': 1.0.0(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.0(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.0(react@18.2.0) + '@radix-ui/react-portal': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.0(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0) + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.4(@types/react@18.2.55)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: @@ -1777,6 +1831,22 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-dismissable-layer@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} peerDependencies: @@ -1829,6 +1899,15 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-focus-guards@1.0.0(react@18.2.0): + resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + react: 18.2.0 + dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -1843,6 +1922,20 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-focus-scope@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} peerDependencies: @@ -1866,6 +1959,16 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-id@1.0.0(react@18.2.0): + resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -1949,6 +2052,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-portal@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-primitive': 1.0.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} peerDependencies: @@ -1970,6 +2085,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} peerDependencies: @@ -1992,6 +2120,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-primitive@1.0.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-slot': 1.0.0(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -2042,6 +2182,16 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-slot@1.0.0(react@18.2.0): + resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-compose-refs': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + /@radix-ui/react-slot@1.0.2(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -2057,6 +2207,15 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0): + resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + react: 18.2.0 + dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -2071,6 +2230,16 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-controllable-state@1.0.0(react@18.2.0): + resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} peerDependencies: @@ -2086,6 +2255,16 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-escape-keydown@1.0.0(react@18.2.0): + resolution: {integrity: sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0) + react: 18.2.0 + dev: false + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} peerDependencies: @@ -2101,6 +2280,15 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0): + resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.23.9 + react: 18.2.0 + dev: false + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} peerDependencies: @@ -3654,6 +3842,19 @@ packages: engines: {node: '>=6'} dev: false + /cmdk@0.2.1(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-U6//9lQ6JvT47+6OF6Gi8BvkxYQ8SCRRSKIJkthIMsFsLZRG0cKvTtuTaefyIKMQb8rvvXy0wGdpTNq/jPtm+g==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@radix-ui/react-dialog': 1.0.0(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + dev: false + /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -6563,6 +6764,25 @@ packages: tslib: 2.6.2 dev: false + /react-remove-scroll@2.5.4(@types/react@18.2.55)(react@18.2.0): + resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.55 + react: 18.2.0 + react-remove-scroll-bar: 2.3.4(@types/react@18.2.55)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.55)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.1(@types/react@18.2.55)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.55)(react@18.2.0) + dev: false + /react-remove-scroll@2.5.5(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} engines: {node: '>=10'} From e92ec878dcfa1508ff41c959fa38ba60c1cd554f Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Mon, 19 Feb 2024 10:02:51 -0600 Subject: [PATCH 02/16] Create root-level CommandK component inside Modal --- src/app/layout.tsx | 2 ++ src/components/CommandK.tsx | 54 +++++++++++++++++++++++++++++++++++++ src/components/Modal.tsx | 20 ++++++++++---- src/components/MoreMenu.tsx | 2 +- 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/components/CommandK.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9aeff4bc..1dd04743 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,6 +13,7 @@ import Footer from '@/site/Footer'; import { Suspense } from 'react'; import FooterClient from '@/site/FooterClient'; import NavClient from '@/site/NavClient'; +import CommandK from '@/components/CommandK'; import '../site/globals.css'; @@ -97,6 +98,7 @@ export default function RootLayout({ + ); diff --git a/src/components/CommandK.tsx b/src/components/CommandK.tsx new file mode 100644 index 00000000..a42187f2 --- /dev/null +++ b/src/components/CommandK.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { Command } from 'cmdk'; +import { useEffect, useState } from 'react'; +import Modal from './Modal'; +import { clsx } from 'clsx/lite'; + +const LISTENER_KEYDOWN = 'keydown'; + +export default function CommandK() { + const [open, setOpen] = useState(false); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + document.addEventListener(LISTENER_KEYDOWN, down); + return () => document.removeEventListener(LISTENER_KEYDOWN, down); + }, []); + + const renderItem = (item: string) => + + {item} + ; + + return ( + + setOpen(false)} fast> + + + No results found. + + {renderItem('a')} + {renderItem('b')} + + {renderItem('c')} + + + {renderItem('Apple')} + + + + ); +} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 289789d5..7c879bd4 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -11,10 +11,14 @@ import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion'; export default function Modal({ onClosePath, + onClose, children, + fast, }: { onClosePath?: string + onClose?: () => void children: ReactNode + fast?: boolean }) { const router = useRouter(); @@ -32,10 +36,16 @@ export default function Modal({ useClickInsideOutside({ htmlElements, - onClickOutside: () => router.push( - onClosePath ?? PATH_ROOT, - { scroll: false }, - ), + onClickOutside: () => { + if (onClose) { + onClose(); + } else { + router.push( + onClosePath ?? PATH_ROOT, + { scroll: false }, + ); + } + }, }); return ( @@ -51,7 +61,7 @@ export default function Modal({ transition={{ duration: 0.3, easing: 'easeOut' }} > Date: Mon, 19 Feb 2024 12:23:33 -0600 Subject: [PATCH 03/16] Seed basic command-k data --- package.json | 3 +- pnpm-lock.yaml | 12 ++++ src/app/layout.tsx | 2 +- src/components/CommandK.tsx | 54 -------------- src/components/CommandKClient.tsx | 116 ++++++++++++++++++++++++++++++ src/components/Modal.tsx | 10 ++- src/site/CommandK.tsx | 53 ++++++++++++++ 7 files changed, 193 insertions(+), 57 deletions(-) delete mode 100644 src/components/CommandK.tsx create mode 100644 src/components/CommandKClient.tsx create mode 100644 src/site/CommandK.tsx diff --git a/package.json b/package.json index 6d9cdb21..936095c7 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "sonner": "^1.4.0", "tailwindcss": "3.4.1", "ts-exif-parser": "^0.2.2", - "typescript": "5.3.3" + "typescript": "5.3.3", + "use-debounce": "^10.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0d7071d..08dc4266 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ dependencies: typescript: specifier: 5.3.3 version: 5.3.3 + use-debounce: + specifier: ^10.0.0 + version: 10.0.0(react@18.2.0) packages: @@ -7569,6 +7572,15 @@ packages: tslib: 2.6.2 dev: false + /use-debounce@10.0.0(react@18.2.0): + resolution: {integrity: sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==} + engines: {node: '>= 16.0.0'} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + /use-sidecar@1.1.2(@types/react@18.2.55)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1dd04743..6e40f293 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,7 +13,7 @@ import Footer from '@/site/Footer'; import { Suspense } from 'react'; import FooterClient from '@/site/FooterClient'; import NavClient from '@/site/NavClient'; -import CommandK from '@/components/CommandK'; +import CommandK from '@/site/CommandK'; import '../site/globals.css'; diff --git a/src/components/CommandK.tsx b/src/components/CommandK.tsx deleted file mode 100644 index a42187f2..00000000 --- a/src/components/CommandK.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; - -import { Command } from 'cmdk'; -import { useEffect, useState } from 'react'; -import Modal from './Modal'; -import { clsx } from 'clsx/lite'; - -const LISTENER_KEYDOWN = 'keydown'; - -export default function CommandK() { - const [open, setOpen] = useState(false); - - useEffect(() => { - const down = (e: KeyboardEvent) => { - if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { - e.preventDefault(); - setOpen((open) => !open); - } - }; - document.addEventListener(LISTENER_KEYDOWN, down); - return () => document.removeEventListener(LISTENER_KEYDOWN, down); - }, []); - - const renderItem = (item: string) => - - {item} - ; - - return ( - - setOpen(false)} fast> - - - No results found. - - {renderItem('a')} - {renderItem('b')} - - {renderItem('c')} - - - {renderItem('Apple')} - - - - ); -} diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx new file mode 100644 index 00000000..c3bc9dbd --- /dev/null +++ b/src/components/CommandKClient.tsx @@ -0,0 +1,116 @@ +'use client'; + +import { Command } from 'cmdk'; +import { useEffect, useState } from 'react'; +import Modal from './Modal'; +import { clsx } from 'clsx/lite'; +import { useDebounce } from 'use-debounce'; +import Spinner from './Spinner'; +import { useRouter } from 'next/navigation'; + +const LISTENER_KEYDOWN = 'keydown'; + +export default function CommandKClient({ + isLoading, + onQueryChange, + sections = [], +}: { + isLoading?: boolean + onQueryChange?: (query: string) => void + sections?: { + heading: string + items: { + label: string + path: string + }[] + }[] +}) { + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(''); + const [queryDebounced] = useDebounce(query, 1000); + + const router = useRouter(); + + useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + document.addEventListener(LISTENER_KEYDOWN, down); + return () => document.removeEventListener(LISTENER_KEYDOWN, down); + }, []); + + useEffect(() => { + if (queryDebounced) { + onQueryChange?.(queryDebounced); + } + }, [queryDebounced, onQueryChange]); + + return ( + + setOpen(false)} + fast + > +
+
+ Search for photos, tags, cameras, and film +
+
+ setQuery(e.currentTarget.value)} + className="w-full !min-w-0" + style={{ paddingRight: '2rem' }} + /> + {isLoading && + + + } +
+ + + + ); +} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 7c879bd4..646b4d84 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -12,11 +12,15 @@ import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion'; export default function Modal({ onClosePath, onClose, + className, + anchor = 'center', children, fast, }: { onClosePath?: string onClose?: () => void + className?: string + anchor?: 'top' | 'center' children: ReactNode fast?: boolean }) { @@ -51,7 +55,10 @@ export default function Modal({ return ( []), + getUniqueCamerasCached().catch(() => []), + getUniqueFilmSimulationsCached().catch(() => []), + ]); + + return ({ + label: tag, + path: pathForTag(tag), + })), + }, { + heading: 'Cameras', + items: cameras.map(({ camera }) => ({ + label: formatCameraText(camera), + path: pathForCamera(camera), + })), + }, { + heading: 'Film Simulations', + items: filmSimulations.map(({ simulation }) => ({ + label: simulation, + path: pathForFilmSimulation(simulation), + })), + }, + ]} + />; +} From 7fd878edf668d410df083b6fc19d7bd90292e4d1 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Mon, 19 Feb 2024 12:55:26 -0600 Subject: [PATCH 04/16] Add themes to command-k --- src/components/CommandKClient.tsx | 49 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/components/CommandKClient.tsx b/src/components/CommandKClient.tsx index c3bc9dbd..c04b4e2a 100644 --- a/src/components/CommandKClient.tsx +++ b/src/components/CommandKClient.tsx @@ -7,9 +7,19 @@ import { clsx } from 'clsx/lite'; import { useDebounce } from 'use-debounce'; import Spinner from './Spinner'; import { useRouter } from 'next/navigation'; +import { useTheme } from 'next-themes'; const LISTENER_KEYDOWN = 'keydown'; +export type CommandKSection = { + heading: string + items: { + label: string + path?: string + action?: () => void + }[] +} + export default function CommandKClient({ isLoading, onQueryChange, @@ -17,18 +27,14 @@ export default function CommandKClient({ }: { isLoading?: boolean onQueryChange?: (query: string) => void - sections?: { - heading: string - items: { - label: string - path: string - }[] - }[] + sections?: CommandKSection[] }) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(''); const [queryDebounced] = useDebounce(query, 1000); + const { setTheme } = useTheme(); + const router = useRouter(); useEffect(() => { @@ -78,12 +84,26 @@ export default function CommandKClient({ aria-hidden="true" className={clsx( 'absolute bottom-4 inset-x-0 h-6 z-10 pointer-events-none', - 'bg-gradient-to-t from-white to-transparent', + 'bg-gradient-to-t to-transparent', + 'from-white dark:from-black', )} /> No results found. - {sections + {[{ + heading: 'Theme', + items: [{ + label: 'System', + action: () => setTheme('system'), + }, { + label: 'Light', + action: () => setTheme('light'), + }, { + label: 'Dark', + action: () => setTheme('dark'), + }], + } as CommandKSection] + .concat(sections) .filter(({ items }) => items.length > 0) .map(({ heading, items }) => - {items.map(({ label, path }) => + {items.map(({ label, path, action }) => { - setOpen(false); - router.push(path); + action?.(); + if (path) { + setOpen(false); + router.push(path); + } }} > {label} From 3992a426fba13c6907fa634a395e10c553391c87 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Mon, 19 Feb 2024 12:57:24 -0600 Subject: [PATCH 05/16] Move command-k inside theme state provider --- src/app/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6e40f293..fc091c62 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -92,13 +92,13 @@ export default function RootLayout({