From 04afc2289c36dc124d73d02870ac0fcd148265f8 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 14 Dec 2025 16:17:54 +0900 Subject: [PATCH] =?UTF-8?q?#176=20=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 43 +++++++ frontend/package.json | 1 + frontend/src/components/TagDetailSidebar.tsx | 123 ++++++++++--------- frontend/src/components/TagSidebar.tsx | 79 +++++++----- frontend/src/components/TopNav.tsx | 78 +++++++----- 5 files changed, 204 insertions(+), 120 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b241081..43fbc44 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -17,6 +17,7 @@ "camelcase-keys": "^9.1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.26", "humps": "^2.0.1", "lucide-react": "^0.511.0", "markdown-it": "^14.1.0", @@ -3573,6 +3574,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", + "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -5216,6 +5244,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 747f7ed..cbe44ff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "camelcase-keys": "^9.1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "framer-motion": "^12.23.26", "humps": "^2.0.1", "lucide-react": "^0.511.0", "markdown-it": "^14.1.0", diff --git a/frontend/src/components/TagDetailSidebar.tsx b/frontend/src/components/TagDetailSidebar.tsx index da2c3b8..0006738 100644 --- a/frontend/src/components/TagDetailSidebar.tsx +++ b/frontend/src/components/TagDetailSidebar.tsx @@ -1,3 +1,4 @@ +import { AnimatePresence, motion } from 'framer-motion' import { useEffect, useState } from 'react' import TagLink from '@/components/TagLink' @@ -18,18 +19,23 @@ const renderTagTree = ( tag: Tag, nestLevel: number, path: string, - ): ReactNode[] => { +): ReactNode[] => { const key = `${ path }-${ tag.id }` const self = ( -
  • + -
  • ) + ) return [self, - ...(tag.children - ?.sort ((a, b) => a.name < b.name ? -1 : 1) - .flatMap (child => renderTagTree (child, nestLevel + 1, key)) ?? [])] + ...((tag.children + ?.sort ((a, b) => a.name < b.name ? -1 : 1) + .flatMap (child => renderTagTree (child, nestLevel + 1, key))) + ?? [])] } @@ -70,55 +76,60 @@ export default (({ post }: Props) => { return ( - {CATEGORIES.map ((cat: Category) => cat in tags && ( -
    - {categoryNames[cat]} -
      - {tags[cat].map (tag => renderTagTree (tag, 0, `cat-${ cat }`))} -
    -
    ))} - {post && ( -
    - 情報 -
      -
    • Id.: {post.id}
    • - {/* TODO: uploadedUser の取得を対応したらコメント外す */} - {/* -
    • - <>耕作者: - {post.uploadedUser - ? ( - - {post.uploadedUser.name || '名もなきニジラー'} - ) - : 'bot操作'} -
    • - */} -
    • 耕作日時: {(new Date (post.createdAt)).toLocaleString ()}
    • -
    • - <>リンク: - - {post.url} - -
    • -
    • - {/* TODO: 表示形式きしょすぎるので何とかする */} - <>オリジナルの投稿日時: - {!(post.originalCreatedFrom) && !(post.originalCreatedBefore) - ? '不明' - : ( - <> - {post.originalCreatedFrom - && `${ (new Date (post.originalCreatedFrom)).toLocaleString () } 以降 `} - {post.originalCreatedBefore - && `${ (new Date (post.originalCreatedBefore)).toLocaleString () } より前`} - )} -
    • -
    -
    )} + + {CATEGORIES.map ((cat: Category) => cat in tags && ( + + {categoryNames[cat]} + + + + {tags[cat].map (tag => renderTagTree (tag, 0, `cat-${ cat }`))} + + + ))} + {post && ( +
    + 情報 +
      +
    • Id.: {post.id}
    • + {/* TODO: uploadedUser の取得を対応したらコメント外す */} + {/* +
    • + <>耕作者: + {post.uploadedUser + ? ( + + {post.uploadedUser.name || '名もなきニジラー'} + ) + : 'bot操作'} +
    • + */} +
    • 耕作日時: {(new Date (post.createdAt)).toLocaleString ()}
    • +
    • + <>リンク: + + {post.url} + +
    • +
    • + {/* TODO: 表示形式きしょすぎるので何とかする */} + <>オリジナルの投稿日時: + {!(post.originalCreatedFrom) && !(post.originalCreatedBefore) + ? '不明' + : ( + <> + {post.originalCreatedFrom + && `${ (new Date (post.originalCreatedFrom)).toLocaleString () } 以降 `} + {post.originalCreatedBefore + && `${ (new Date (post.originalCreatedBefore)).toLocaleString () } より前`} + )} +
    • +
    +
    )} +
    ) }) satisfies FC diff --git a/frontend/src/components/TagSidebar.tsx b/frontend/src/components/TagSidebar.tsx index a2e00b2..735490d 100644 --- a/frontend/src/components/TagSidebar.tsx +++ b/frontend/src/components/TagSidebar.tsx @@ -1,4 +1,5 @@ import axios from 'axios' +import { AnimatePresence, motion } from 'framer-motion' import { useEffect, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' @@ -8,7 +9,6 @@ import SectionTitle from '@/components/common/SectionTitle' import SidebarComponent from '@/components/layout/SidebarComponent' import { API_BASE_URL } from '@/config' import { CATEGORIES } from '@/consts' -import { cn } from '@/lib/utils' import type { FC } from 'react' @@ -61,37 +61,52 @@ export default (({ posts }: Props) => { return ( -
    - タグ -
      - {CATEGORIES.flatMap (cat => cat in tags ? ( - tags[cat].map (tag => ( -
    • - -
    • ))) : [])} -
    - 関聯 - {posts.length > 0 && ( - { - ev.preventDefault () - void ((async () => { - try - { - const { data } = await axios.get (`${ API_BASE_URL }/posts/random`, - { params: { tags: tagsQuery.split (' ').filter (e => e !== '').join (' '), - match: (anyFlg ? 'any' : 'all') } }) - navigate (`/posts/${ (data as Post).id }`) - } - catch - { - ; - } - }) ()) - }}> - ランダム - )} -
    + + + {tagsVsbl && ( + + タグ +
      + {CATEGORIES.flatMap (cat => cat in tags ? ( + tags[cat].map (tag => ( +
    • + +
    • ))) : [])} +
    + 関聯 + {posts.length > 0 && ( + { + ev.preventDefault () + void ((async () => { + try + { + const { data } = await axios.get (`${ API_BASE_URL }/posts/random`, + { params: { tags: tagsQuery.split (' ').filter (e => e !== '').join (' '), + match: (anyFlg ? 'any' : 'all') } }) + navigate (`/posts/${ (data as Post).id }`) + } + catch + { + ; + } + }) ()) + }}> + ランダム + )} +
    )} +
    + - {subItem.name} - )))} - ))} - - - + + {menuOpen && ( + + + {menu.map ((item, i) => ( + + { + if (i !== openItemIdx) + { + ev.preventDefault () + setOpenItemIdx (i) + } + }}> + {item.name} + + {i === openItemIdx && ( + item.subMenu + .filter (subItem => subItem.visible ?? true) + .map ((subItem, j) => 'component' in subItem ? subItem.component : ( + + {subItem.name} + )))} + ))} + + + )} + ) }) satisfies FC -- 2.34.1