diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 9302245..0000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,47 +0,0 @@ -home: - - changed-files: - - any-glob-to-any-file: "app/(home)/**" - -post: - - changed-files: - - any-glob-to-any-file: "app/post/**" - -category: - - changed-files: - - any-glob-to-any-file: "app/(category)/**" - -dock: - - changed-files: - - any-glob-to-any-file: "components/dock/**" - -pagination: - - changed-files: - - any-glob-to-any-file: "components/pagination/**" - -widget: - - changed-files: - - any-glob-to-any-file: "components/widgets/**" - -series: - - changed-files: - - any-glob-to-any-file: "components/series/**" - -comments: - - changed-files: - - any-glob-to-any-file: "components/comments/**" - -storybook: - - changed-files: - - any-glob-to-any-file: "stories/**" - -infra: - - changed-files: - - any-glob-to-any-file: ".github/**" - -test: - - changed-files: - - any-glob-to-any-file: "tests/**" - -util: - - changed-files: - - any-glob-to-any-file: "lib/**" diff --git a/.github/labels.yml b/.github/labels.yml deleted file mode 100644 index c15c138..0000000 --- a/.github/labels.yml +++ /dev/null @@ -1,45 +0,0 @@ -- name: feat - color: "1d76db" - description: New feature -- name: chore - color: "cccccc" - description: Chore / infra -- name: test - color: "0e8a16" - description: Testing -- name: a11y - color: "5319e7" - description: Accessibility -- name: home - color: "0052cc" - description: Home page -- name: post - color: "fbca04" - description: Post detail -- name: category - color: "d93f0b" - description: Category page -- name: dock - color: "5319e7" - description: DockMenu -- name: comments - color: "0366d6" - description: Giscus / Comments -- name: widget - color: "0b7285" - description: Widgets / metrics -- name: series - color: "a2eeef" - description: Series -- name: pagination - color: "7057ff" - description: Pagination -- name: util - color: "6f42c1" - description: Utils / helpers -- name: infra - color: "24292e" - description: Infra / CI -- name: storybook - color: "ededed" - description: Storybook diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92f27f7..ef5564c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,6 @@ jobs: - run: pnpm typecheck - run: pnpm lint - - run: pnpm test:ci - run: pnpm build diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index 37ae8e6..0000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: PR Labeler -on: - pull_request_target: - types: [opened, synchronize, reopened] - -permissions: - contents: read - pull-requests: write - -jobs: - label: - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v5 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - configuration-path: .github/labeler.yml diff --git a/.github/workflows/labels-sync.yml b/.github/workflows/labels-sync.yml deleted file mode 100644 index 907e1c6..0000000 --- a/.github/workflows/labels-sync.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Sync labels -on: - workflow_dispatch: - push: - paths: - - .github/labels.yml - -permissions: - issues: write - contents: read - -jobs: - labels: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - sparse-checkout: .github/labels.yml - - uses: EndBug/label-sync@v2 - with: - config-file: .github/labels.yml - delete-other-labels: false diff --git a/content/posts/INSIGHT/velite-test.mdx b/content/posts/INSIGHT/velite-test.mdx index e4daf52..cc51b39 100644 --- a/content/posts/INSIGHT/velite-test.mdx +++ b/content/posts/INSIGHT/velite-test.mdx @@ -6,7 +6,7 @@ category: "Insight" series: "Nextjs" tags: ["Mdx", "Velite", "Rsc"] summary: "App Router에서 MDX가 Server Component 트리로 렌더링되는 흐름을 정리." -thumbnail: "/images/thumbnails/rsc-mdx.png" +thumbnail: "" draft: false --- diff --git a/package.json b/package.json index e31ee8d..b42d9e2 100644 --- a/package.json +++ b/package.json @@ -17,15 +17,19 @@ "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", "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", - "react-icons": "^5.5.0" + "react-icons": "^5.5.0", + "rehype-autolink-headings": "^7.1.0", + "rehype-slug": "^6.0.0" }, "devDependencies": { "@svgr/webpack": "^8.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f7c433..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) @@ -35,6 +41,12 @@ importers: react-icons: specifier: ^5.5.0 version: 5.5.0(react@19.2.0) + rehype-autolink-headings: + specifier: ^7.1.0 + version: 7.1.0 + rehype-slug: + specifier: ^6.0.0 + version: 6.0.0 devDependencies: '@svgr/webpack': specifier: ^8.1.0 @@ -1350,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==} @@ -1940,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==} @@ -2164,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'} @@ -2337,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==} @@ -2739,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'} @@ -2758,6 +2980,9 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2838,6 +3063,12 @@ packages: hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + hast-util-heading-rank@3.0.0: + resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} @@ -3964,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'} @@ -4025,6 +4286,9 @@ packages: resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true + rehype-autolink-headings@7.1.0: + resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} + rehype-parse@9.0.1: resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} @@ -4037,6 +4301,9 @@ packages: rehype-recma@1.0.0: resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + rehype-slug@6.0.0: + resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} + remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -4573,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==} @@ -6140,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': @@ -6750,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 @@ -7026,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: {} @@ -7181,6 +7643,8 @@ snapshots: detect-newline@3.1.0: {} + detect-node-es@1.1.0: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -7780,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: @@ -7799,6 +8265,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-slugger@2.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7893,6 +8361,14 @@ snapshots: vfile-location: 5.0.3 web-namespaces: 2.0.1 + hast-util-heading-rank@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-parse-selector@4.0.0: dependencies: '@types/hast': 3.0.4 @@ -9512,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: @@ -9605,6 +10108,15 @@ snapshots: dependencies: jsesc: 3.1.0 + rehype-autolink-headings@7.1.0: + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.3.0 + hast-util-heading-rank: 3.0.0 + hast-util-is-element: 3.0.0 + unified: 11.0.5 + unist-util-visit: 5.0.0 + rehype-parse@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -9629,6 +10141,14 @@ snapshots: transitivePeerDependencies: - supports-color + rehype-slug@6.0.0: + dependencies: + '@types/hast': 3.0.4 + github-slugger: 2.0.0 + hast-util-heading-rank: 3.0.0 + hast-util-to-string: 3.0.1 + unist-util-visit: 5.0.0 + remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -10296,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/public/error/404.svg b/public/error/404.svg new file mode 100644 index 0000000..d1b9548 --- /dev/null +++ b/public/error/404.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/error/double-arrow-icon.svg b/public/error/double-arrow-icon.svg new file mode 100644 index 0000000..f345a6d --- /dev/null +++ b/public/error/double-arrow-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/error/elephant.svg b/public/error/elephant.svg new file mode 100644 index 0000000..ec8f5fa --- /dev/null +++ b/public/error/elephant.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/error/oops.svg b/public/error/oops.svg new file mode 100644 index 0000000..e2dfb2e --- /dev/null +++ b/public/error/oops.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/error/wip-card.svg b/public/error/wip-card.svg new file mode 100644 index 0000000..f0734ef --- /dev/null +++ b/public/error/wip-card.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/post-fallback.png b/public/post-fallback.png new file mode 100644 index 0000000..5f18c81 Binary files /dev/null and b/public/post-fallback.png differ diff --git a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesFolder.tsx b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesFolder.tsx index 2687efa..243f382 100644 --- a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesFolder.tsx +++ b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesFolder.tsx @@ -8,6 +8,17 @@ interface Props { } export default function SeriesFolder({ visible, activeId, onSelect }: Props) { + const handleClick = (id: string) => { + onSelect(id); + const target = document.getElementById("series-post-section"); + if (target) { + target.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + } + }; + return ( @@ -22,7 +33,7 @@ export default function SeriesFolder({ visible, activeId, onSelect }: Props) { postCount={series.postCount} tone={series.tone} isActive={isActive} - onClick={() => onSelect(series.id)} + onClick={() => handleClick(series.id)} /> ); })} diff --git a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx index 7a9de2b..195dfe3 100644 --- a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx +++ b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx @@ -32,7 +32,7 @@ export default function SeriesPostList({ const seriesMeta = series ? getSeriesMeta(series) : null; return ( - + {seriesMeta?.name ?? "모아보기"} diff --git a/src/app/(layout)/(shell)/_components/dock/DockMenu.tsx b/src/app/(layout)/(shell)/_components/dock/DockMenu.tsx index e01d479..ff586c6 100644 --- a/src/app/(layout)/(shell)/_components/dock/DockMenu.tsx +++ b/src/app/(layout)/(shell)/_components/dock/DockMenu.tsx @@ -5,15 +5,17 @@ import { DOCK_NAV_VARIANTS, } from "@/app/(layout)/(shell)/_components/dock/_constants/dock.motion"; import { DOCK_ITEMS } from "@/app/(layout)/(shell)/_components/dock/_constants/dockItem.config"; -import { useDockCloseHint } from "@/app/(layout)/(shell)/_components/dock/hook/useDockCloseHint"; -import { useDockInteraction } from "@/app/(layout)/(shell)/_components/dock/hook/useDockInteraction"; +import { useDockCloseHint } from "@/app/(layout)/(shell)/_components/dock/_hooks/useDockCloseHint"; +import { useDockInteraction } from "@/app/(layout)/(shell)/_components/dock/_hooks/useDockInteraction"; +import { WipFeatureOverlay } from "@/components/common/WipFeatureOverlay"; +import { useWipFeatureNotice } from "@/hooks/useWipFeatureNotice"; import clsx from "clsx"; import { AnimatePresence, motion } from "framer-motion"; import { ChevronsDown } from "lucide-react"; import { DEFAULT_STYLE } from "./_constants/dock.config"; +import { useDockMenu } from "./_hooks/useDockMenu"; import { getDockClasses } from "./dock.utils"; import { DockMenuItem } from "./DockMenuItem"; -import { useDockMenu } from "./hook/useDockMenu"; export default function DockMenu() { const { @@ -43,6 +45,10 @@ export default function DockMenu() { hideDelay: 800, }); + const { isOpen, openNotice, closeNotice } = useWipFeatureNotice({ + autoCloseMs: 2500, + }); + if (isHidden) return null; return ( @@ -82,6 +88,11 @@ export default function DockMenu() { const style = iconStyles[index] ?? DEFAULT_STYLE; + const handleItemClick = + item.type === "button" && item.label === "PHOTOBOOTH" + ? () => openNotice() + : undefined; + return ( { itemRefs.current[index] = el; }} + onClick={handleItemClick} /> ); })} @@ -142,6 +154,8 @@ export default function DockMenu() { )} + + ); } diff --git a/src/app/(layout)/(shell)/_components/dock/DockMenuItem.tsx b/src/app/(layout)/(shell)/_components/dock/DockMenuItem.tsx index 7d99949..0e18621 100644 --- a/src/app/(layout)/(shell)/_components/dock/DockMenuItem.tsx +++ b/src/app/(layout)/(shell)/_components/dock/DockMenuItem.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { DOCK_CONFIG } from "./_constants/dock.config"; import { DockMenuItemProps } from "./dock.types"; -export const DockMenuItem = ({ item, style, onRef }: DockMenuItemProps) => { +export const DockMenuItem = ({ item, style, onRef, onClick, }: DockMenuItemProps) => { const renderContent = () => { const imgElement = ( @@ -44,7 +44,7 @@ export const DockMenuItem = ({ item, style, onRef }: DockMenuItemProps) => { diff --git a/src/app/(layout)/(shell)/_components/dock/hook/useDockCloseHint.ts b/src/app/(layout)/(shell)/_components/dock/_hooks/useDockCloseHint.ts similarity index 100% rename from src/app/(layout)/(shell)/_components/dock/hook/useDockCloseHint.ts rename to src/app/(layout)/(shell)/_components/dock/_hooks/useDockCloseHint.ts diff --git a/src/app/(layout)/(shell)/_components/dock/hook/useDockInteraction.ts b/src/app/(layout)/(shell)/_components/dock/_hooks/useDockInteraction.ts similarity index 100% rename from src/app/(layout)/(shell)/_components/dock/hook/useDockInteraction.ts rename to src/app/(layout)/(shell)/_components/dock/_hooks/useDockInteraction.ts diff --git a/src/app/(layout)/(shell)/_components/dock/hook/useDockMenu.ts b/src/app/(layout)/(shell)/_components/dock/_hooks/useDockMenu.ts similarity index 100% rename from src/app/(layout)/(shell)/_components/dock/hook/useDockMenu.ts rename to src/app/(layout)/(shell)/_components/dock/_hooks/useDockMenu.ts diff --git a/src/app/(layout)/(shell)/_components/dock/_tests_/DockMenu.test.tsx b/src/app/(layout)/(shell)/_components/dock/_tests_/DockMenu.test.tsx deleted file mode 100644 index 7bd784c..0000000 --- a/src/app/(layout)/(shell)/_components/dock/_tests_/DockMenu.test.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { render, screen } from "@testing-library/react"; -import DockMenu from "../DockMenu"; - -describe("DockMenu", () => { - beforeEach(() => { - render(); - }); - test("독메뉴 네비게이션이 렌더링된다", () => { - expect( - screen.getByRole("navigation", { name: /Dock menu/i }) - ).toBeInTheDocument(); - }); - - test("모든 메뉴 아이템이 aria-label 기준으로 렌더링된다", () => { - const labels = ["DEV_LOG", "INSIGHT", "JOURNAL", "CONTACT", "PHOTOBOOTH"]; - - labels.forEach((label) => { - expect(screen.getByLabelText(label)).toBeInTheDocument(); - }); - }); - - test("CONTACT 아이콘은 mailto 링크를 가진다", () => { - const contact = screen.getByLabelText("CONTACT"); - expect(contact.closest("a")).toHaveAttribute( - "href", - "mailto:musamea99@gmail.com" - ); - }); - - test("초기 상태는 expanded로 모든 메뉴 아이템이 렌더링된다", () => { - const labels = ["DEV_LOG", "INSIGHT", "JOURNAL", "CONTACT", "PHOTOBOOTH"]; - - labels.forEach((label) => { - expect(screen.getByLabelText(label)).toBeInTheDocument(); - }); - }); -}); diff --git a/src/app/(layout)/(shell)/_components/dock/_tests_/dock.utils.test.ts b/src/app/(layout)/(shell)/_components/dock/_tests_/dock.utils.test.ts deleted file mode 100644 index 663cbfd..0000000 --- a/src/app/(layout)/(shell)/_components/dock/_tests_/dock.utils.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { DOCK_CONFIG } from "../_constants/dock.config"; -import { calculateIconStyle, getDockClasses } from "../dock.utils"; - -describe("calculateIconStyle", () => { - const baseRect = { - left: 100, - width: 40, - } as DOMRect; - - test("마우스가 아이콘 중앙에 있을 때 최대 scale과 translateY가 적용된다", () => { - const centerX = baseRect.left + baseRect.width / 2; - const { scale, translateY } = calculateIconStyle(centerX, baseRect); - - expect(scale).toBeCloseTo(DOCK_CONFIG.SCALE_MAX); - expect(translateY).toBeCloseTo(DOCK_CONFIG.TRANSLATE_Y_MAX); - }); - - test("마우스가 멀리 있을 때 최소 scale과 0에 가까운 translateY가 적용된다", () => { - const farX = - baseRect.left + baseRect.width / 2 + DOCK_CONFIG.MOUSE_MAX_DISTANCE * 10; - - const { scale, translateY } = calculateIconStyle(farX, baseRect); - - expect(scale).toBeCloseTo(DOCK_CONFIG.SCALE_MIN); - expect(translateY).toBeCloseTo(0); - }); - - test("마우스가 중간 거리일 때 scale과 translateY가 선형 보간된다", () => { - const centerX = baseRect.left + baseRect.width / 2; - const halfDistanceX = centerX + DOCK_CONFIG.MOUSE_MAX_DISTANCE / 2; - - const { scale, translateY } = calculateIconStyle(halfDistanceX, baseRect); - - const expectedT = 0.5; - const expectedScale = - DOCK_CONFIG.SCALE_MIN + - expectedT * (DOCK_CONFIG.SCALE_MAX - DOCK_CONFIG.SCALE_MIN); - - const expectedTranslateY = DOCK_CONFIG.TRANSLATE_Y_MAX * expectedT; - - expect(scale).toBeCloseTo(expectedScale); - expect(translateY).toBeCloseTo(expectedTranslateY); - }); -}); - -describe("getDockClasses", () => { - test("expanded 상태에서는 넉넉한 패딩과 불쿠명도 클래스 포함", () => { - const classes = getDockClasses("expanded"); - - expect(classes).toContain("px-7"); - expect(classes).toContain("py-3"); - expect(classes).toContain("opacity-100"); - expect(classes).toContain("translate-y-0"); - }); - - test("collapsed 상태에서는 작은 패딩과 반투명, hover 효과 클래스가 포함된다", () => { - const classes = getDockClasses("collapsed"); - - expect(classes).toContain("px-2"); - expect(classes).toContain("py-2"); - expect(classes).toContain("opacity-50"); - expect(classes).toContain("hover:opacity-90"); - expect(classes).toContain("cursor-pointer"); - }); -}); diff --git a/src/app/(layout)/(shell)/_components/dock/dock.types.ts b/src/app/(layout)/(shell)/_components/dock/dock.types.ts index a3e9230..d0e63b7 100644 --- a/src/app/(layout)/(shell)/_components/dock/dock.types.ts +++ b/src/app/(layout)/(shell)/_components/dock/dock.types.ts @@ -46,4 +46,5 @@ export interface DockMenuItemProps { item: Exclude; style: IconStyle; onRef: (el: HTMLLIElement | null) => void; + onClick?: () => void; } \ No newline at end of file diff --git a/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx b/src/app/(layout)/(shell)/_components/home/HeroLeft.tsx index 7ed830b..bb90de7 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) { - - - + + + + + + 삼킨 것들은 지금 무엇이 되었을까요? + - {/* 인기 태그 섹션 */} - - - Trending : - - + + ⌘K + + ); diff --git a/src/app/(layout)/(shell)/_components/home/HeroRightMedia.tsx b/src/app/(layout)/(shell)/_components/home/HeroRightMedia.tsx index 4674594..7373c07 100644 --- a/src/app/(layout)/(shell)/_components/home/HeroRightMedia.tsx +++ b/src/app/(layout)/(shell)/_components/home/HeroRightMedia.tsx @@ -1,14 +1,18 @@ "use client"; +import Image from "next/image"; + export default function HeroRightMedia() { return ( - - {/* 배경 효과 (리퀴드 글래스, 글로우 등은 여기서 스타일링) */} - - - - + - ) + ); } 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) => ( - - {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/SiteFooter.tsx b/src/app/(layout)/(shell)/_components/layout/SiteFooter.tsx index 4f8d7f2..4b9b8f7 100644 --- a/src/app/(layout)/(shell)/_components/layout/SiteFooter.tsx +++ b/src/app/(layout)/(shell)/_components/layout/SiteFooter.tsx @@ -4,7 +4,7 @@ import { FaGithub } from "react-icons/fa"; export default function SiteFooter() { return ( -