-
+
排序
@@ -113,8 +107,7 @@ export function PostListWithControls({ posts, pageSize }: Props) {
搜尋文章
-
-
+ {align === 'end' ? (
+
+ ) : (
+
+ )}
{label}
diff --git a/components/post-toc.tsx b/components/post-toc.tsx
index 6a6908b..c944fef 100644
--- a/components/post-toc.tsx
+++ b/components/post-toc.tsx
@@ -2,8 +2,7 @@
import { useEffect, useRef, useState } from 'react';
import { usePathname } from 'next/navigation';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faListUl } from '@fortawesome/free-solid-svg-icons';
+import { FiList } from 'react-icons/fi';
interface TocItem {
id: string;
@@ -101,7 +100,7 @@ export function PostToc({ onLinkClick }: { onLinkClick?: () => void }) {
return (
diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx
index 408f911..5f872f0 100644
--- a/components/theme-toggle.tsx
+++ b/components/theme-toggle.tsx
@@ -2,8 +2,7 @@
import { useEffect, useState } from 'react';
import { useTheme } from 'next-themes';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons';
+import { FiMoon, FiSun } from 'react-icons/fi';
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
@@ -27,12 +26,11 @@ export function ThemeToggle() {
onClick={() => setTheme(next)}
aria-label={theme === 'dark' ? '切換為淺色主題' : '切換為深色主題'}
>
-
+ {isDark ? (
+
+ ) : (
+
+ )}
);
}
diff --git a/next-env.d.ts b/next-env.d.ts
index c4b7818..9edff1c 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/package-lock.json b/package-lock.json
index b1a749a..f6f9279 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,14 +10,9 @@
"license": "ISC",
"dependencies": {
"@emotion/is-prop-valid": "^1.4.0",
- "@fortawesome/fontawesome-svg-core": "^7.1.0",
- "@fortawesome/free-brands-svg-icons": "^7.1.0",
- "@fortawesome/free-solid-svg-icons": "^7.1.0",
- "@fortawesome/react-fontawesome": "^3.1.0",
"@vercel/og": "^0.8.5",
"clsx": "^2.1.1",
"contentlayer2": "^0.5.8",
- "framer-motion": "^12.23.24",
"gray-matter": "^4.0.3",
"markdown-wasm": "^1.2.0",
"next": "^16.0.3",
@@ -25,6 +20,7 @@
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-icons": "^5.5.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-pretty-code": "^0.14.1",
"rehype-slug": "^6.0.0",
@@ -534,7 +530,6 @@
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
"integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@emotion/memoize": "^0.9.0"
}
@@ -1126,65 +1121,6 @@
"integrity": "sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==",
"license": "MIT"
},
- "node_modules/@fortawesome/fontawesome-common-types": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.1.0.tgz",
- "integrity": "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@fortawesome/fontawesome-svg-core": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz",
- "integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "7.1.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@fortawesome/free-brands-svg-icons": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.1.0.tgz",
- "integrity": "sha512-9byUd9bgNfthsZAjBl6GxOu1VPHgBuRUP9juI7ZoM98h8xNPTCTagfwUFyYscdZq4Hr7gD1azMfM9s5tIWKZZA==",
- "license": "(CC-BY-4.0 AND MIT)",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "7.1.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@fortawesome/free-solid-svg-icons": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.1.0.tgz",
- "integrity": "sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==",
- "license": "(CC-BY-4.0 AND MIT)",
- "dependencies": {
- "@fortawesome/fontawesome-common-types": "7.1.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@fortawesome/react-fontawesome": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.1.0.tgz",
- "integrity": "sha512-5OUQH9aDH/xHJwnpD4J7oEdGvFGJgYnGe0UebaPIdMW9UxYC/f5jv2VjVEgnikdJN0HL8yQxp9Nq+7gqGZpIIA==",
- "license": "MIT",
- "engines": {
- "node": ">=20"
- },
- "peerDependencies": {
- "@fortawesome/fontawesome-svg-core": "~6 || ~7",
- "react": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@grpc/grpc-js": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.1.tgz",
@@ -5756,33 +5692,6 @@
"url": "https://github.com/sponsors/rawify"
}
},
- "node_modules/framer-motion": {
- "version": "12.23.24",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
- "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
- "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",
@@ -8555,21 +8464,6 @@
"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",
@@ -9489,6 +9383,15 @@
"react": "^19.2.0"
}
},
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/package.json b/package.json
index 571dec9..8c9f4ed 100644
--- a/package.json
+++ b/package.json
@@ -17,14 +17,9 @@
"type": "module",
"dependencies": {
"@emotion/is-prop-valid": "^1.4.0",
- "@fortawesome/fontawesome-svg-core": "^7.1.0",
- "@fortawesome/free-brands-svg-icons": "^7.1.0",
- "@fortawesome/free-solid-svg-icons": "^7.1.0",
- "@fortawesome/react-fontawesome": "^3.1.0",
"@vercel/og": "^0.8.5",
"clsx": "^2.1.1",
"contentlayer2": "^0.5.8",
- "framer-motion": "^12.23.24",
"gray-matter": "^4.0.3",
"markdown-wasm": "^1.2.0",
"next": "^16.0.3",
@@ -32,6 +27,7 @@
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-icons": "^5.5.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-pretty-code": "^0.14.1",
"rehype-slug": "^6.0.0",
diff --git a/styles/globals.css b/styles/globals.css
index ab8b81d..4b08c1e 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -233,6 +233,69 @@ body {
left: 0;
}
+/* TOC transitions - replaces Framer Motion */
+.toc-sidebar {
+ transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
+ will-change: opacity, transform;
+}
+
+.toc-sidebar-enter {
+ opacity: 0;
+ transform: translateX(20px);
+}
+
+.toc-sidebar-enter-active {
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.toc-sidebar-exit {
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.toc-sidebar-exit-active {
+ opacity: 0;
+ transform: translateX(20px);
+}
+
+.toc-mobile {
+ transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
+ will-change: opacity, transform;
+}
+
+.toc-mobile-enter {
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.toc-mobile-enter-active {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.toc-mobile-exit {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.toc-mobile-exit-active {
+ opacity: 0;
+ transform: translateY(20px);
+}
+
+.toc-button {
+ transition: all 0.2s ease-in-out;
+}
+
+.toc-button:active {
+ transform: scale(0.95);
+}
+
+.toc-button:hover {
+ transform: scale(1.05);
+}
+
@layer components {
.type-display {
font-size: clamp(2.2rem, 1.6rem + 2.4vw, 3.5rem);