| @@ -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", | |||
| @@ -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", | |||
| @@ -1,3 +1,4 @@ | |||
| import { AnimatePresence, motion } from 'framer-motion' | |||
| import { useEffect, useState } from 'react' | |||
| import TagLink from '@/components/TagLink' | |||
| @@ -22,14 +23,19 @@ const renderTagTree = ( | |||
| const key = `${ path }-${ tag.id }` | |||
| const self = ( | |||
| <li key={key} className="mb-1"> | |||
| <motion.li | |||
| key={key} | |||
| layout | |||
| transition={{ duration: .2, ease: 'easeOut' }} | |||
| className="mb-1"> | |||
| <TagLink tag={tag} nestLevel={nestLevel}/> | |||
| </li>) | |||
| </motion.li>) | |||
| return [self, | |||
| ...(tag.children | |||
| ...((tag.children | |||
| ?.sort ((a, b) => a.name < b.name ? -1 : 1) | |||
| .flatMap (child => renderTagTree (child, nestLevel + 1, key)) ?? [])] | |||
| .flatMap (child => renderTagTree (child, nestLevel + 1, key))) | |||
| ?? [])] | |||
| } | |||
| @@ -70,13 +76,17 @@ export default (({ post }: Props) => { | |||
| return ( | |||
| <SidebarComponent> | |||
| <TagSearch/> | |||
| <motion.div layout> | |||
| {CATEGORIES.map ((cat: Category) => cat in tags && ( | |||
| <div className="my-3" key={cat}> | |||
| <motion.div layout className="my-3" key={cat}> | |||
| <SubsectionTitle>{categoryNames[cat]}</SubsectionTitle> | |||
| <ul> | |||
| <motion.ul layout> | |||
| <AnimatePresence initial={false}> | |||
| {tags[cat].map (tag => renderTagTree (tag, 0, `cat-${ cat }`))} | |||
| </ul> | |||
| </div>))} | |||
| </AnimatePresence> | |||
| </motion.ul> | |||
| </motion.div>))} | |||
| {post && ( | |||
| <div> | |||
| <SectionTitle>情報</SectionTitle> | |||
| @@ -120,5 +130,6 @@ export default (({ post }: Props) => { | |||
| </li> | |||
| </ul> | |||
| </div>)} | |||
| </motion.div> | |||
| </SidebarComponent>) | |||
| }) satisfies FC<Props> | |||
| @@ -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,7 +61,20 @@ export default (({ posts }: Props) => { | |||
| return ( | |||
| <SidebarComponent> | |||
| <TagSearch/> | |||
| <div className={cn (!(tagsVsbl) && 'hidden', 'md:block mt-4')}> | |||
| <AnimatePresence initial={false}> | |||
| {tagsVsbl && ( | |||
| <motion.div | |||
| key="sptags" | |||
| className="md:block mt-4" | |||
| variants={{ hidden: { clipPath: 'inset(0 0 100% 0)', | |||
| height: 0 }, | |||
| visible: { clipPath: 'inset(0 0 0% 0)', | |||
| height: 'auto'} }} | |||
| initial="hidden" | |||
| animate="visible" | |||
| exit="hidden" | |||
| transition={{ duration: .2, ease: 'easeOut' }}> | |||
| <SectionTitle>タグ</SectionTitle> | |||
| <ul> | |||
| {CATEGORIES.flatMap (cat => cat in tags ? ( | |||
| @@ -91,7 +104,9 @@ export default (({ posts }: Props) => { | |||
| }}> | |||
| ランダム | |||
| </a>)} | |||
| </div> | |||
| </motion.div>)} | |||
| </AnimatePresence> | |||
| <a href="#" | |||
| className="md:hidden block my-2 text-center text-sm | |||
| text-gray-500 hover:text-gray-400 | |||
| @@ -1,5 +1,6 @@ | |||
| import axios from 'axios' | |||
| import toCamel from 'camelcase-keys' | |||
| import { AnimatePresence, motion } from 'framer-motion' | |||
| import { Fragment, useState, useEffect } from 'react' | |||
| import { Link, useLocation } from 'react-router-dom' | |||
| @@ -136,8 +137,20 @@ export default (({ user }: Props) => { | |||
| </Link>))} | |||
| </div> | |||
| <div className={cn (menuOpen ? 'flex flex-col md:hidden' : 'hidden', | |||
| 'bg-yellow-200 dark:bg-red-975 items-start')}> | |||
| <AnimatePresence initial={false}> | |||
| {menuOpen && ( | |||
| <motion.div | |||
| key="spmenu" | |||
| className={cn ('flex flex-col md:hidden', | |||
| 'bg-yellow-200 dark:bg-red-975 items-start')} | |||
| variants={{ closed: { clipPath: 'inset(0 0 100% 0)', | |||
| height: 0 }, | |||
| open: { clipPath: 'inset(0 0 0% 0)', | |||
| height: 'auto' } }} | |||
| initial="closed" | |||
| animate="open" | |||
| exit="closed" | |||
| transition={{ duration: .2, ease: 'easeOut' }}> | |||
| <Separator/> | |||
| {menu.map ((item, i) => ( | |||
| <Fragment key={i}> | |||
| @@ -167,6 +180,7 @@ export default (({ user }: Props) => { | |||
| </Fragment>))} | |||
| <TopNavUser user={user} sp/> | |||
| <Separator/> | |||
| </div> | |||
| </motion.div>)} | |||
| </AnimatePresence> | |||
| </>) | |||
| }) satisfies FC<Props> | |||