From affa7e8b91ec47ad37b08bf8437161eaba4d61a6 Mon Sep 17 00:00:00 2001 From: choiboa Date: Tue, 13 Jan 2026 22:18:44 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat:=20=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + pnpm-lock.yaml | 487 ++++++++++++++++++ .../(shell)/_components/home/HeroLeft.tsx | 58 ++- .../(shell)/_components/home/HeroTagList.tsx | 20 - .../home/_hooks/useSearchTransition.ts | 52 ++ .../(shell)/_components/layout/SiteHeader.tsx | 158 ++++-- src/app/(layout)/(shell)/_constants/home.ts | 11 +- src/app/(layout)/layout.tsx | 5 +- src/components/common/CommandPalette.tsx | 111 ++++ src/hooks/useCommandPaletteInternal.ts | 169 ++++++ 10 files changed, 975 insertions(+), 98 deletions(-) delete mode 100644 src/app/(layout)/(shell)/_components/home/HeroTagList.tsx create mode 100644 src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts create mode 100644 src/components/common/CommandPalette.tsx create mode 100644 src/hooks/useCommandPaletteInternal.ts diff --git a/package.json b/package.json index 9b91dc8..b42d9e2 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,10 @@ "prepare": "husky" }, "dependencies": { + "@radix-ui/react-dialog": "^1.1.15", "@supabase/supabase-js": "^2.80.0", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "framer-motion": "^12.23.24", "lucide-react": "^0.553.0", "next": "^15.5.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ded1531..c0dce56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,18 @@ importers: .: dependencies: + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@supabase/supabase-js': specifier: ^2.80.0 version: 2.80.0 clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) framer-motion: specifier: ^12.23.24 version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -1356,6 +1362,199 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + 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.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + 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-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + 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.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + 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-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + 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-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + 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-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + 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-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + 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.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + 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-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + 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-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + 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-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + 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-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + 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-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -1946,6 +2145,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -2170,6 +2373,12 @@ 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 + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -2343,6 +2552,9 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -2745,6 +2957,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -3979,6 +4195,36 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@19.2.0: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} @@ -4594,6 +4840,26 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6161,6 +6427,165 @@ snapshots: '@pkgr/core@0.2.9': {} + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.2(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + '@rtsao/scc@1.1.0': {} '@shikijs/core@3.20.0': @@ -6771,6 +7196,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -7047,6 +7476,18 @@ snapshots: clsx@2.1.1: {} + cmdk@1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + co@4.6.0: {} collapse-white-space@2.1.0: {} @@ -7202,6 +7643,8 @@ snapshots: detect-newline@3.1.0: {} + detect-node-es@1.1.0: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -7801,6 +8244,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-package-type@0.1.0: {} get-proto@1.0.1: @@ -9543,6 +9988,33 @@ snapshots: react-is@18.3.1: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + react-remove-scroll@2.7.2(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + + react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + get-nonce: 1.0.1 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + react@19.2.0: {} read-pkg@3.0.0: @@ -10344,6 +10816,21 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + util-deprecate@1.0.2: {} v8-compile-cache-lib@3.0.1: {} diff --git a/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx b/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx index 7ed830b..9bda519 100644 --- a/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx +++ b/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx @@ -1,15 +1,18 @@ -import SearchBar from "../../../../../components/ui/SearchBar"; -import { HERO_POPULAR_TAGS } from "../../_constants/home"; -import HeroTagList from "./HeroTagList"; +"use client"; + +import { useCommandPalette } from "@/components/common/CommandPalette"; +import clsx from "clsx"; +import { Search } from "lucide-react"; interface HeroLeftProps { intro: string; } export default function HeroLeft({ intro }: HeroLeftProps) { + const { openPalette } = useCommandPalette(); + return (
- {/* 소개 타이틀/텍스트 */}

Like the Boa That @@ -21,16 +24,45 @@ export default function HeroLeft({ intro }: HeroLeftProps) {

-
- -
+
+
); diff --git a/src/app/(layout)/(shell)/_components/home/HeroTagList.tsx b/src/app/(layout)/(shell)/_components/home/HeroTagList.tsx deleted file mode 100644 index 731c888..0000000 --- a/src/app/(layout)/(shell)/_components/home/HeroTagList.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client" - -interface HeroTagListProps { - tags: readonly string[]; -} - -export default function HeroTagList({ tags }: HeroTagListProps) { - return ( -
- {tags.map((tag) => ( - - ))} -
- ) -} diff --git a/src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts b/src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts new file mode 100644 index 0000000..a711de4 --- /dev/null +++ b/src/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition.ts @@ -0,0 +1,52 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; +import { useCommandPalette } from "@/components/common/CommandPalette"; + +export function useSearchTransition() { + const pathname = usePathname(); + const isHome = pathname === "/"; + const { openPalette } = useCommandPalette(); + + const [pastThreshold, setPastThreshold] = useState(false); + + useEffect(() => { + if (!isHome) { + return; + } + + const threshold = 320; + + const handleScroll = () => { + setPastThreshold(window.scrollY > threshold); + }; + + handleScroll(); + + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, [isHome]); + + const showSearch = !isHome || pastThreshold; + + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + + const toggleMobileMenu = () => + setIsMobileMenuOpen((prev) => !prev); + + const closeMobileMenu = () => setIsMobileMenuOpen(false); + + const handleSearchClick = () => { + openPalette(); + }; + + return { + isHome, + showSearch, + isMobileMenuOpen, + toggleMobileMenu, + closeMobileMenu, + handleSearchClick, + }; +} diff --git a/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx b/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx index 6667515..fe124f2 100644 --- a/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx +++ b/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx @@ -1,84 +1,136 @@ "use client"; - -import { Menu, X } from "lucide-react"; +import { useSearchTransition } from "@/app/(layout)/(shell)/_components/home/_hooks/useSearchTransition"; +import clsx from "clsx"; +import { AnimatePresence, motion } from "framer-motion"; +import { Menu, Search, X } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; -import { useState } from "react"; - import DarkModeWheel from "../../../../../components/ui/DarkModeWheel"; export default function SiteHeader() { - const [open, setOpen] = useState(false); + const { + showSearch, + isMobileMenuOpen, + toggleMobileMenu, + closeMobileMenu, + handleSearchClick, + } = useSearchTransition(); return ( -
-
- - B-log - +
+
+
+ + B-log + + + + {showSearch && ( + + +
+ 검색 + + ⌘K + +
+
+ )} +
+
- {/* 데스크탑 버전 */} -
-
- {/* 모바일 메뉴 */} -
- -
+ + + {isMobileMenuOpen && ( + + + + )} +
); } diff --git a/src/app/(layout)/(shell)/_constants/home.ts b/src/app/(layout)/(shell)/_constants/home.ts index 4d3e014..45bebe7 100644 --- a/src/app/(layout)/(shell)/_constants/home.ts +++ b/src/app/(layout)/(shell)/_constants/home.ts @@ -1,11 +1,2 @@ -export const HERO_BRANDING = "Slice your day, piece by piece."; - export const HERO_INTRO = - `차분히 바라보고, 섬세하게 만들고, 꾸준히 다듬습니다.\n그 과정에서 얻은 생각들을 이곳에 기록합니다.`; - - -export const HERO_POPULAR_TAGS = [ - "Next.js", - "Testing", - "Front-End", -] as const; \ No newline at end of file + `어렵고 거대한 문제도 가리지 않고 통째로 삼킵니다. \n차분히 바라보고, 섬세하게 다듬고, 꾸준히 소화하는 과정에서 얻은 것들을 이곳에 기록합니다.`; diff --git a/src/app/(layout)/layout.tsx b/src/app/(layout)/layout.tsx index db8afdb..6581796 100644 --- a/src/app/(layout)/layout.tsx +++ b/src/app/(layout)/layout.tsx @@ -1,11 +1,12 @@ import SiteHeader from "@/app/(layout)/(shell)/_components/layout/SiteHeader"; +import { CommandPaletteProvider } from "@/components/common/CommandPalette"; export default function Layout({ children }: { children: React.ReactNode }) { return ( - <> +
{children}
- +
); } diff --git a/src/components/common/CommandPalette.tsx b/src/components/common/CommandPalette.tsx new file mode 100644 index 0000000..35ab97a --- /dev/null +++ b/src/components/common/CommandPalette.tsx @@ -0,0 +1,111 @@ +"use client"; + +import useCommandPaletteInternal from "@/hooks/useCommandPaletteInternal"; +import * as Dialog from "@radix-ui/react-dialog"; +import { Command } from "cmdk"; +import { Search } from "lucide-react"; +import { createContext, useContext, type ReactNode } from "react"; + +export type CommandItem = { + id: string; + label: string; + hint?: string; + href?: string; + searchableText?: string; +}; + +type CommandPaletteContextValue = { + openPalette: () => void; + closePalette: () => void; + togglePalette: () => void; +}; + +interface CommandPaletteProviderProps { + children: ReactNode; +} + +const CommandPaletteContext = createContext( + null +); + +export function useCommandPalette() { + const ctx = useContext(CommandPaletteContext); + if (!ctx) { + throw new Error( + "useCommandPalette must be used within CommandPaletteProvider" + ); + } + return ctx; +} + +export function CommandPaletteProvider({ + children, +}: CommandPaletteProviderProps) { + const { + open, + query, + filteredItems, + openPalette, + closePalette, + togglePalette, + setQuery, + handleSelect, + handleOpenChange, + } = useCommandPaletteInternal(); + + return ( + + + + + + 사이트 검색 + + +
+ + + + ESC + +
+ + + + 검색 결과가 없습니다. + + + + {filteredItems.map((item) => ( + handleSelect(item)} + className="flex cursor-pointer flex-col gap-0.5 rounded-xl px-3 py-2 text-sm aria-selected:bg-background/10" + > + {item.label} + {item.hint && ( + + {item.hint} + + )} + + ))} + + +
+
+
+
+ {children} +
+ ); +} diff --git a/src/hooks/useCommandPaletteInternal.ts b/src/hooks/useCommandPaletteInternal.ts new file mode 100644 index 0000000..e0b914c --- /dev/null +++ b/src/hooks/useCommandPaletteInternal.ts @@ -0,0 +1,169 @@ +import { CommandItem } from "@/components/common/CommandPalette"; +import { getAllPosts } from "@/lib/posts"; +import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +type UseCommandPaletteResult = { + open: boolean; + query: string; + items: CommandItem[]; + filteredItems: CommandItem[]; + openPalette: () => void; + closePalette: () => void; + togglePalette: () => void; + setQuery: (v: string) => void; + handleSelect: (item: CommandItem) => void; + handleOpenChange: (next: boolean) => void; +}; + +const STATIC_ITEMS: CommandItem[] = [ + { id: "home", label: "홈", hint: "홈 화면으로 이동합니다.", href: "/" }, + { + id: "Dev-log", + label: "개발 로그", + hint: "프로젝트 진행 중의 엔지니어링 히스토리를 기록한 공간입니다.", + href: "/dev-log", + }, + { + id: "Insight", + label: "Tech Notes", + hint: "기술 개념과 인사이트를 정리해둔 아카이브입니다.", + href: "/insight", + }, + { + id: "Journal", + label: "회고/저널", + hint: "지나온 경험을 정리하고 방향성을 고민하는 개인 저널입니다.", + href: "/journal", + }, +]; + +function buildPostItems(): CommandItem[] { + const posts = getAllPosts({ includeDrafts: false }); + + return posts.map((post) => { + const tagsText = post.tags?.join(" ") ?? ""; + const categoryText = post.category ?? ""; + const seriesText = post.series ?? ""; + const summaryText = post.summary ?? ""; + const slugText = post.slug ?? ""; + + const searchableText = [ + post.title, + summaryText, + tagsText, + categoryText, + seriesText, + slugText, + ] + .filter(Boolean) + .join(" ") + .toLowerCase(); + + return { + id: `post-${post.slug}`, + label: post.title, + hint: + summaryText || + `${categoryText} · ${seriesText || ""} ${tagsText || ""}`.trim(), + href: `/posts/${post.slug}`, + searchableText, + }; + }); +} + +export default function useCommandPaletteInternal(): UseCommandPaletteResult { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(""); + + const items = useMemo(() => { + const postItems = buildPostItems(); + return [...STATIC_ITEMS, ...postItems]; + }, []); + + const filteredItems = useMemo(() => { + const q = query.trim().toLowerCase(); + if (!q) return items; + + return items.filter((item) => { + const target = ( + item.label + + " " + + (item.hint ?? "") + + " " + + (item.searchableText ?? "") + ) + .toLowerCase() + .trim(); + + return target.includes(q); + }); + }, [items, query]); + + const openPalette = useCallback(() => { + setQuery(""); + setOpen(true); + }, []); + + const closePalette = useCallback(() => { + setOpen(false); + setQuery(""); + }, []); + + const togglePalette = useCallback(() => { + setOpen((prev) => { + const next = !prev; + if (next) { + setQuery(""); + } + return next; + }); + }, []); + + const handleSelect = useCallback( + (item: CommandItem) => { + if (item.href) { + router.push(item.href); + } + setOpen(false); + setQuery(""); + }, + [router] + ); + + const handleOpenChange = useCallback((next: boolean) => { + setOpen(next); + if (!next) { + setQuery(""); + } + }, []); + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + const isMac = navigator.platform.toLowerCase().includes("mac"); + const meta = isMac ? e.metaKey : e.ctrlKey; + + if (meta && e.key.toLowerCase() === "k") { + e.preventDefault(); + togglePalette(); + } + }; + + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [togglePalette]); + + return { + open, + query, + items, + filteredItems, + openPalette, + closePalette, + togglePalette, + setQuery, + handleSelect, + handleOpenChange, + }; +} \ No newline at end of file From 36282972414d2afddc45b235b7247bf489695183 Mon Sep 17 00:00:00 2001 From: choiboa Date: Tue, 13 Jan 2026 22:23:04 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=92=84=20Style=20:=20=EA=B3=BC?= =?UTF-8?q?=EB=8F=84=ED=95=9C=20=EC=88=98=EC=B9=98=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(layout)/(shell)/_components/home/HeroLeft.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx b/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx index 9bda519..bb90de7 100644 --- a/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx +++ b/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx @@ -32,7 +32,7 @@ export default function HeroLeft({ intro }: HeroLeftProps) { "cursor-pointer", "group flex w-full items-center gap-3 rounded-2xl px-4 py-3 shadow-sm backdrop-blur-sm transition", "bg-white/60 text-neutral-700 border border-neutral-200 hover:bg-white/80 hover:border-neutral-300/80", - "dark:bg-white/10 dark:text-white/60 dark:border-white/20 dark:hover:bg-white/60 dark:hover:border-white/40" + "dark:bg-white/10 dark:text-white/60 dark:border-white/20 dark:hover:bg-white/20 dark:hover:border-white/40" )} > Date: Tue, 13 Jan 2026 22:29:09 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Remove=20:=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/_tests_/SiteFooter.test.tsx | 16 -------- .../layout/_tests_/SiteHeader.test.tsx | 39 ------------------- 2 files changed, 55 deletions(-) delete mode 100644 src/app/(layout)/(shell)/_components/layout/_tests_/SiteFooter.test.tsx delete mode 100644 src/app/(layout)/(shell)/_components/layout/_tests_/SiteHeader.test.tsx diff --git a/src/app/(layout)/(shell)/_components/layout/_tests_/SiteFooter.test.tsx b/src/app/(layout)/(shell)/_components/layout/_tests_/SiteFooter.test.tsx deleted file mode 100644 index 2b47096..0000000 --- a/src/app/(layout)/(shell)/_components/layout/_tests_/SiteFooter.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import SiteFooter from "../SiteFooter"; - -describe("SiteFooter 테스트", () => { - test("카피라이트 텍스트가 렌더링된다", () => { - render(); - - expect(screen.getByText(/b0o0a/i)).toBeInTheDocument(); - }); - - test("Github 링크가 렌더링된다", () => { - render(); - - expect(screen.getByRole("link", { name: /깃허브로 이동/i })).toBeInTheDocument(); - }) -}) \ No newline at end of file diff --git a/src/app/(layout)/(shell)/_components/layout/_tests_/SiteHeader.test.tsx b/src/app/(layout)/(shell)/_components/layout/_tests_/SiteHeader.test.tsx deleted file mode 100644 index 0c9aca9..0000000 --- a/src/app/(layout)/(shell)/_components/layout/_tests_/SiteHeader.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import SiteHeader from "../SiteHeader"; - -jest.mock("next-themes", () => ({ - useTheme: () => ({ - theme: "light", - resolvedTheme: "light", - setTheme: jest.fn(), - }) -})) - -describe("SiteHeader", () => { - test("로고가 렌더링된다", () => { - render(); - - const logos = screen.getAllByAltText(/b-log/i); - expect(logos.length).toBeGreaterThan(0); - }); - - test("주요 네비게이션 메뉴가 렌더링된다", () => { - render(); - - const resumeLinks = screen.getAllByRole("link", { name: "Resume" }); - const guestbookLinks = screen.getAllByRole("link", { name: "Guestbook" }); - const labLinks = screen.getAllByRole("link", { name: "Lab" }); - - expect(resumeLinks.length).toBeGreaterThan(0); - expect(guestbookLinks.length).toBeGreaterThan(0); - expect(labLinks.length).toBeGreaterThan(0); - }); - - test("테마 토글 버튼이 렌더링된다", () => { - render(); - - expect( - screen.getByRole("button", { name: /테마 변경/i }) - ).toBeInTheDocument(); - }); -});