diff --git a/package-lock.json b/package-lock.json index 1f22463..c59f896 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,9 +22,11 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-toast": "^1.1.5", "@tanstack/react-query": "^4.36.1", + "avvvatars-react": "^0.4.2", "axios": "^1.5.1", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", + "kmenu": "^2.0.0", "lucide-react": "^0.376.0", "next": "^13.5.6", "next-auth": "^4.23.2", @@ -3034,6 +3036,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/avvvatars-react": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/avvvatars-react/-/avvvatars-react-0.4.2.tgz", + "integrity": "sha512-D/bnSM+P6pQi71dFeFVqQsqmv+ct5XG7uO2lvJoiksALpXLd6kPgpRR1SUQxFZJkJNrkmg0NPgbhIgJgOsDYRQ==", + "license": "MIT", + "dependencies": { + "goober": "^2.1.8" + }, + "peerDependencies": { + "react": ">= 16", + "react-dom": ">= 16" + } + }, "node_modules/axe-core": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", @@ -3657,7 +3672,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -5328,6 +5342,31 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.9.0.tgz", + "integrity": "sha512-nCfGxvsQecVLjjYDu35G2F5ls+ArE3FBfhxV0RSiisMaUKqteq5DMBFNRKwMyVj+VqKTNhawt+BV480YCHKFlQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -5646,6 +5685,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6634,6 +6682,22 @@ "node": ">=0.10.0" } }, + "node_modules/kmenu": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kmenu/-/kmenu-2.0.0.tgz", + "integrity": "sha512-ZuNZDFrQZIP/C7GK7xd+CxX9J5UEkXK20X7OhjcH/yBa/zDQEklo5Wk36iHJoSMeHvJ3uW0q/VBKjXk0SV2v7w==", + "license": "MIT", + "dependencies": { + "framer-motion": "^11.0.3", + "react-scrollbar-size": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -8019,6 +8083,18 @@ } } }, + "node_modules/react-scrollbar-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-scrollbar-size/-/react-scrollbar-size-5.0.0.tgz", + "integrity": "sha512-Ly3OuRMz4yDFViTh+ANH6TrG8EqrgjC1uxxm2a/95+2Ijy3XT+bWtzm4QmgZUcUVg+8BCKzmPMM7z39ZtucDIQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, "node_modules/react-share": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-share/-/react-share-5.1.0.tgz", diff --git a/package.json b/package.json index f4bb98d..c5bd6b2 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "axios": "^1.5.1", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", + "kmenu": "^2.0.0", "lucide-react": "^0.376.0", "next": "^13.5.6", "next-auth": "^4.23.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1987dd3..c35f0d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: clsx: specifier: ^2.0.0 version: 2.1.1 + kmenu: + specifier: ^2.0.0 + version: 2.0.0(@emotion/is-prop-valid@1.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) lucide-react: specifier: ^0.376.0 version: 0.376.0(react@18.3.1) @@ -1859,6 +1862,20 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + framer-motion@11.9.0: + resolution: {integrity: sha512-nCfGxvsQecVLjjYDu35G2F5ls+ArE3FBfhxV0RSiisMaUKqteq5DMBFNRKwMyVj+VqKTNhawt+BV480YCHKFlQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fs-extra@11.2.0: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} @@ -2280,6 +2297,12 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + kmenu@2.0.0: + resolution: {integrity: sha512-ZuNZDFrQZIP/C7GK7xd+CxX9J5UEkXK20X7OhjcH/yBa/zDQEklo5Wk36iHJoSMeHvJ3uW0q/VBKjXk0SV2v7w==} + engines: {node: '>=10'} + peerDependencies: + react: '>=16.0.0' + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -2776,6 +2799,12 @@ packages: '@types/react': optional: true + react-scrollbar-size@5.0.0: + resolution: {integrity: sha512-Ly3OuRMz4yDFViTh+ANH6TrG8EqrgjC1uxxm2a/95+2Ijy3XT+bWtzm4QmgZUcUVg+8BCKzmPMM7z39ZtucDIQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.1 || ^18.0.0 + react-share@5.1.0: resolution: {integrity: sha512-OvyfMtj/0UzH1wi90OdHhZVJ6WUC/+IeWvBwppeZozwIGyAjQgyR0QXlHOrxVHVECqnGvcpBaFTXVrqouTieaw==} peerDependencies: @@ -3532,7 +3561,7 @@ snapshots: '@types/node': 20.5.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.5.4) - cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.5.4))(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))(typescript@5.5.4) + cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.5.4))(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.5.4))(typescript@5.5.4) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -4629,7 +4658,7 @@ snapshots: cookie@0.5.0: {} - cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.5.4))(ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4))(typescript@5.5.4): + cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@5.5.4))(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.5.4))(typescript@5.5.4): dependencies: '@types/node': 20.5.1 cosmiconfig: 8.3.6(typescript@5.5.4) @@ -5324,6 +5353,14 @@ snapshots: fraction.js@4.3.7: {} + framer-motion@11.9.0(@emotion/is-prop-valid@1.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + tslib: 2.7.0 + optionalDependencies: + '@emotion/is-prop-valid': 1.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + fs-extra@11.2.0: dependencies: graceful-fs: 4.2.11 @@ -5721,6 +5758,15 @@ snapshots: kind-of@6.0.3: {} + kmenu@2.0.0(@emotion/is-prop-valid@1.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + framer-motion: 11.9.0(@emotion/is-prop-valid@1.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-scrollbar-size: 5.0.0(react@18.3.1) + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - react-dom + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -6186,6 +6232,10 @@ snapshots: optionalDependencies: '@types/react': 18.3.5 + react-scrollbar-size@5.0.0(react@18.3.1): + dependencies: + react: 18.3.1 + react-share@5.1.0(react@18.3.1): dependencies: classnames: 2.5.1 diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c5f993a..937f4d7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,3 +1,5 @@ +import "@/styles/variables/kmenu.css"; +import "kmenu/dist/cmdk.css"; import "./globals.css"; import type { Metadata } from "next"; diff --git a/src/app/provider.tsx b/src/app/provider.tsx index 81c35a7..eede66d 100644 --- a/src/app/provider.tsx +++ b/src/app/provider.tsx @@ -1,3 +1,7 @@ +"use client"; +import { MenuProvider } from "kmenu"; + +import CommandPalette from "@/components/layout/header/command-palette"; import AuthProvider from "@/providers/auth-provider"; import ReactQueryProvider from "@/providers/react-query-provider"; @@ -8,7 +12,12 @@ interface Properties { export default function Providers({ children }: Properties): JSX.Element { return ( - {children} + + + {children} + + + ); } diff --git a/src/components/layout/header/command-palette.tsx b/src/components/layout/header/command-palette.tsx new file mode 100644 index 0000000..8fdfc1e --- /dev/null +++ b/src/components/layout/header/command-palette.tsx @@ -0,0 +1,101 @@ +"use client"; +/* eslint-disable @typescript-eslint/strict-boolean-expressions */ +import { + type Command, + CommandMenu, + CommandWrapper, + type InnerCommand, + useCommands, + useKmenu, +} from "kmenu"; +import { Github, Instagram, Search, Twitter, Youtube } from "lucide-react"; +import { type FC, useState } from "react"; + +import { defaultPostsConstants } from "../../../constants/post.constants.ts"; + +const CommandPalette: FC = () => { + const [postSearchResults, setPostSearchResults] = useState( + [], + ); + const { setOpen, open } = useKmenu(); + + const getPostsWithQuery = (query?: string): InnerCommand[] => { + + const filteredPosts = defaultPostsConstants.filter( + (defaultPostsConstant) => + defaultPostsConstant.header + .toLowerCase() + .includes(query?.toLowerCase() ?? "") || + defaultPostsConstant.content + .toLowerCase() + .includes(query?.toLowerCase() ?? ""), + ); + const commands = filteredPosts.map((post) => ({ + icon: , + text: post.header, + perform: () => { + // handleSearch(post.header); + }, + })); + setPostSearchResults(commands); + return commands; + }; + + const main: Command[] = [ + { + category: "Search", + commands: [ + { + icon: , + text: "Search", + perform: () => { + setOpen(2); + }, + }, + ], + }, + { + category: "Social", + commands: [ + { + icon: , + text: "Twitter", + perform: () => window.open('https://x.com/gelecekbilimde', '_blank'), + }, + { + icon: , + text: "Instagram", + perform: () => window.open('https://www.instagram.com/gelecekbilimde/', '_blank'), + }, + { + icon: , + text: "Youtube", + perform: () => window.open('https://www.youtube.com/gelecekbilimde', '_blank'), + }, + ], + }, + ]; + + const posts: Command[] = [ + { + category: "Posts", + commands: postSearchResults, + }, + ]; + + const [mainCommands] = useCommands(main); + const [postsCommands] = useCommands(posts); + + return ( + + + + + ); +}; + +export default CommandPalette; diff --git a/src/components/layout/header/index.tsx b/src/components/layout/header/index.tsx index dfc551f..fa489e3 100644 --- a/src/components/layout/header/index.tsx +++ b/src/components/layout/header/index.tsx @@ -2,6 +2,7 @@ import "react-modern-drawer/dist/index.css"; +import { useKmenu } from "kmenu"; import { MenuIcon, SearchIcon } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; diff --git a/src/styles/variables/kmenu.css b/src/styles/variables/kmenu.css new file mode 100644 index 0000000..2cff6e7 --- /dev/null +++ b/src/styles/variables/kmenu.css @@ -0,0 +1,99 @@ +.kmenu { + --backdrop-background: hsla(0, 0%, 100%, 0.565); + --backdrop-blur: blur(2px); + + --menu-background: hsla(0, 0%, 100%, 1); + --menu-shadow: 0px 0px 60px 10px hsla(0, 0%, 0%, 0.05); + --menu-border: 1px solid #e2e2e2; + --menu-border-radius: 10px; + --menu-padding: 0.5rem; + --menu-transition: 100ms ease; + --menu-width: 600px; + + --search-border: 1px solid #e2e2e2; + --search-color: hsla(0, 0%, 7%, 1); + --search-font-size: 16px; + + --breadcrumb-font-size: 12px; + --breadcrumb-background: #fafafa; + --breadcrumb-color: #666666; + --breadcrumb-radius: 4px; + --breadcrumb-margin: 5px 0px 0px 5px; + --breadcrumb-padding: 5px 10px; + --breadcrumb-border: 1px solid #e2e2e2; + + --command-font-size: 14px; + --command-icon-size: 16px; + --command-icon-margin: 8px; + --command-transition: 100ms linear; + --command-inactive-color: hsla(0, 0%, 51%, 1); + --command-active-color: hsla(0, 0%, 20%, 1); + --command-margin: 0px 0px 0px 14px; + + --command-heading-font-size: 12px; + --command-heading-color: #666666; + --command-heading-margin: 5px 0 10px 10px; + + --shortcut-inactive-color: hsla(0, 0%, 51%, 1); + --shortcut-active-color: hsla(0, 0%, 20%, 1); + --shortcut-inactive-background: hsla(0, 0%, 100%, 0); + --shortcut-active-background: hsla(0, 0%, 50%, 0.075); + --shortcut-border: 1px solid hsla(0, 0%, 89%, 1); + --shortcut-border-radius: 3px; + --shortcut-font-size: 12px; + --shortcut-size: 26px; + + --checkbox-width: 20px; + --checkbox-height: 20px; + --checkbox-border-radius: 6px; + --checkbox-inactive-background: hsla(0, 0%, 100%, 0); + --checkbox-active-background: hsla(0, 0%, 100%, 0); + --checkbox-checked-background: hsla(210, 100%, 72%, 1); + --checkbox-checked-border: 1px solid hsla(210, 100%, 72%, 1); + --checkbox-inactive-border: 1px solid hsla(0, 0%, 89%, 1); + --checkbox-active-border: 1px solid #d5d5d5; + --checkbox-check-color: hsla(0, 0%, 100%, 1); + --checkbox-transition: 0.05s linear; + + --selected-background: rgba(0, 0, 0, 0.05); + --selected-border-radius: 8px; + --selected-height: 50px; + + --opening-animation-duration: 100ms; +} + +html.dark .kmenu { + --backdrop-background: hsla(0, 0%, 0%, 0.565); + + --menu-background: rgb(19, 19, 19); + --menu-shadow: none; + --menu-border: 1px solid hsla(0, 0%, 15%, 1); + + --search-border: 1px solid hsla(0, 0%, 15%, 1); + --search-color: hsla(0, 0%, 100%, 1); + + --breadcrumb-background: hsla(0, 0%, 14%, 1); + --breadcrumb-color: #666666; + --breadcrumb-border: 1px solid #242424; + + --command-inactive-color: hsla(0, 0%, 40%, 1); + --command-active-color: hsla(0, 0%, 80%, 1); + + --command-heading-color: #666666; + + --shortcut-inactive-color: hsla(0, 0%, 51%, 1); + --shortcut-active-color: hsla(0, 0%, 80%, 1); + --shortcut-inactive-background: hsla(0, 0%, 100%, 0); + --shortcut-active-background: hsla(0, 0%, 10%, 1); + --shortcut-border: 1px solid hsla(0, 0%, 15%, 1); + + --checkbox-inactive-background: hsla(0, 0%, 100%, 0); + --checkbox-active-background: hsla(0, 0%, 100%, 0); + --checkbox-checked-background: hsla(210, 100%, 72%, 1); + --checkbox-checked-border: 1px solid hsla(210, 100%, 72%, 1); + --checkbox-inactive-border: 1px solid hsla(0, 0%, 89%, 1); + --checkbox-active-border: 1px solid #d5d5d5; + --checkbox-check-color: hsla(0, 0%, 100%, 1); + + --selected-background: hsla(0, 0%, 100%, 0.05); +}