Commit c85740f

HPCesia <me@hpcesia.com>
2025-02-13 10:49:45
feat: new swup theme
1 parent c8e401b
src/components/misc/CategoryBar.astro
@@ -11,7 +11,7 @@ interface Props {
 const { categories, currentCategory } = Astro.props;
 ---
 
-<div id="category-bar" class="card border-base-300 mb-4 w-full border-2">
+<div id="category-bar" class="card border-base-300 swup-transition-slide mb-4 w-full border-2">
   <div class="card-body flex flex-row items-center gap-2 overflow-auto px-2 py-3">
     <a
       href={`/`}
@@ -37,3 +37,37 @@ const { categories, currentCategory } = Astro.props;
     }
   </div>
 </div>
+
+<script>
+  import { pathMatch, pathsEqual } from '@utils/url-utils';
+
+  function setup() {
+    const hasCategoryBar = (url: string) =>
+      pathMatch(/\/archives\/categories\/.+\/\d/, url) || pathsEqual(url, '/');
+
+    window.swup?.hooks.before('visit:start', (visit) => {
+      if (hasCategoryBar(visit.to.url)) {
+        const categoryBar = document.getElementById('category-bar');
+        categoryBar?.classList.remove('swup-transition-slide');
+      }
+    });
+    window.swup?.hooks.on('content:replace', (visit) => {
+      if (hasCategoryBar(visit.from.url)) {
+        const categoryBar = document.getElementById('category-bar');
+        categoryBar?.classList.remove('swup-transition-slide');
+      }
+    });
+    window.swup?.hooks.on('animation:in:end', (visit) => {
+      if (hasCategoryBar(visit.from.url)) {
+        const categoryBar = document.getElementById('category-bar');
+        categoryBar?.classList.add('swup-transition-slide');
+      }
+    });
+  }
+
+  if (window.swup) {
+    setup();
+  } else {
+    document.addEventListener('swup:enable', setup);
+  }
+</script>
src/components/PostsPage.astro
@@ -23,7 +23,7 @@ const totalPages = Math.ceil(posts.length / postsPerPage);
 posts = posts.slice((currentPage - 1) * postsPerPage, currentPage * postsPerPage);
 ---
 
-<div id="post-page" class="flex flex-col gap-4">
+<div id="post-page" class="swup-transition-slide flex flex-col gap-4">
   {
     posts.map((post) => {
       const data = post.data;
src/layouts/GlobalLayout.astro
@@ -1,6 +1,7 @@
 ---
 import { profileConfig, searchConfig, siteConfig } from '@/config';
 import '@/styles/global.css';
+import '@/styles/transition.css';
 import type { Favicon } from '@/types/config';
 import Banner from '@components/Banner.astro';
 import Navbar from '@components/Navbar.astro';
src/layouts/MainLayout.astro
@@ -15,7 +15,12 @@ const { title, description, lang } = Astro.props;
   <slot slot="header" name="header" />
   <!-- Main content -->
   <main class="relative mx-auto flex max-w-(--breakpoint-xl) flex-col gap-4">
-    <div class:list={[siteConfig.banner !== false && 'absolute top-0 -translate-y-full']}>
+    <div
+      id="header-content"
+      class:list={[
+        siteConfig.banner !== false && 'swup-transition-slide absolute top-0 -translate-y-full',
+      ]}
+    >
       <slot name="header-content" />
     </div>
     <div class="flex gap-4">
src/layouts/PostPageLayout.astro
@@ -20,7 +20,7 @@ const { title, description, lang, headings, comment } = Astro.props;
 <MainLayout title={title} description={description} lang={lang}>
   <slot slot="head" name="head" />
   <slot slot="header-content" name="header-content" />
-  <div class="card border-base-300 border-2 px-6 py-4">
+  <div class="card border-base-300 swup-transition-fade border-2 px-6 py-4">
     <slot />
     {comment && commentConfig.enable && <Comment />}
   </div>
src/pages/archives/categories/index.astro
@@ -30,7 +30,7 @@ if (uncategorizedPosts.length > 0)
 
 <MainLayout title={i18n(I18nKey.categories)}>
   <div
-    class="card card-border border-base-300 mx-auto flex flex-col items-center border-2 px-6 py-4"
+    class="card card-border border-base-300 swup-transition-fade mx-auto flex flex-col items-center border-2 px-6 py-4"
   >
     <div class="tooltip md:tooltip-right tooltip-bottom mx-auto w-fit">
       <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.categories)}</h1>
src/pages/archives/tags/index.astro
@@ -26,7 +26,7 @@ if (untaggedPosts.length > 0) tagPosts.set(i18n(I18nKey.untagged) as string, unt
 
 <MainLayout title={i18n(I18nKey.tags)}>
   <div
-    class="card card-border border-base-300 mx-auto flex flex-col items-center border-2 px-6 py-4"
+    class="card card-border border-base-300 swup-transition-fade mx-auto flex flex-col items-center border-2 px-6 py-4"
   >
     <div class="tooltip md:tooltip-right tooltip-bottom mx-auto w-fit">
       <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.tags)}</h1><div
src/pages/archives/[...time].astro
@@ -73,7 +73,7 @@ const postCount = await getPostsCount();
 ---
 
 <MainLayout>
-  <div class="card border-base-300 mb-4 border-2">
+  <div id="time-archive-nav" class="card border-base-300 swup-transition-fade mb-4 border-2">
     <div class="breadcrumbs card-body text-md py-3">
       <ul>
         <li>
@@ -100,7 +100,7 @@ const postCount = await getPostsCount();
       </ul>
     </div>
   </div>
-  <div class="card border-base-300 border-2 px-6 py-4">
+  <div class="card border-base-300 swup-transition-fade border-2 px-6 py-4">
     <div class="tooltip md:tooltip-right tooltip-bottom mx-auto w-fit">
       <h1 class="text-center text-3xl font-bold">{i18n(I18nKey.archive)}</h1>
       <div class="tooltip-content">
@@ -272,3 +272,36 @@ const postCount = await getPostsCount();
     }
   </Fragment>
 </MainLayout>
+
+<script>
+  import { pathMatch } from '@utils/url-utils';
+
+  function setup() {
+    const hasArchiveNav = (url: string) => pathMatch(/\/archives(\/\d{4}(\/\d{1,2})?)?$/, url);
+
+    window.swup?.hooks.before('visit:start', (visit) => {
+      if (hasArchiveNav(visit.to.url)) {
+        const archiveNav = document.getElementById('time-archive-nav');
+        archiveNav?.classList.remove('swup-transition-fade');
+      }
+    });
+    window.swup?.hooks.on('content:replace', (visit) => {
+      if (hasArchiveNav(visit.from.url)) {
+        const archiveNav = document.getElementById('time-archive-nav');
+        archiveNav?.classList.remove('swup-transition-fade');
+      }
+    });
+    window.swup?.hooks.on('animation:in:end', (visit) => {
+      if (hasArchiveNav(visit.from.url)) {
+        const archiveNav = document.getElementById('time-archive-nav');
+        archiveNav?.classList.add('swup-transition-fade');
+      }
+    });
+  }
+
+  if (window.swup) {
+    setup();
+  } else {
+    document.addEventListener('swup:enable', setup);
+  }
+</script>
src/styles/transition.css
@@ -0,0 +1,29 @@
+@reference './global.css';
+
+html.is-changing .swup-transition-fade {
+  @apply transition-all duration-200;
+}
+html.is-animating .swup-transition-fade {
+  @apply translate-y-4 opacity-0;
+}
+
+html.is-changing .swup-transition-slide {
+  @apply transition-all duration-300;
+}
+html.is-animating .swup-transition-slide {
+  @apply -translate-x-20 opacity-0;
+}
+
+.onload-animation {
+  opacity: 0;
+  animation: 300ms fade-in-up;
+  animation-fill-mode: forwards;
+}
+
+#header-content {
+  animation-delay: 400ms;
+}
+
+#main-content {
+  animation-delay: 300ms;
+}
src/utils/url-utils.ts
@@ -10,12 +10,7 @@ export function pathsEqual(path1: string, path2: string) {
 }
 
 export function pathMatch(regex: RegExp, path: string) {
-  const normalizedPath = path
-    .split('?')[0]
-    .split('#')[0]
-    .replace(/^\/|\/$/g, '')
-    .toLowerCase();
-  return regex.test(normalizedPath) || regex.test(getRelativeUrl(normalizedPath));
+  return regex.test(path) || regex.test(getRelativeUrl(path));
 }
 
 export function getRelativeUrl(path: string) {
astro.config.mjs
@@ -34,6 +34,7 @@ export default defineConfig({
     pagefind(),
     mdx(),
     swup({
+      theme: false,
       containers: ['main'],
       animationClass: 'swup-transition-',
       globalInstance: true,