From 09934a9f8e7173e5f199c3e6afbb8281893024f1 Mon Sep 17 00:00:00 2001
From: Bad Manners <me@badmanners.xyz>
Date: Sat, 7 Sep 2024 18:28:08 -0300
Subject: [PATCH] Add Tippy.js tooltips to focusable elements

---
 package-lock.json                             | 20 +++++++++
 package.json                                  |  1 +
 .../tag-categories/9-type-of-content.yaml     |  2 +-
 src/layouts/GalleryLayout.astro               | 15 +++++++
 src/layouts/PublishedContentLayout.astro      | 42 +++++++++----------
 src/pages/games.astro                         |  1 +
 src/pages/index.astro                         |  7 +++-
 src/pages/stories/[page].astro                |  1 +
 src/pages/tags.astro                          |  1 +
 src/styles/base.css                           | 10 ++---
 tailwind.config.mjs                           |  2 +-
 11 files changed, 73 insertions(+), 29 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 1a78914..4898f6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,7 @@
         "sanitize-html": "^2.13.0",
         "tailwindcss": "^3.4.9",
         "tiny-decode": "^0.1.3",
+        "tippy.js": "^6.3.7",
         "toml": "^3.0.0",
         "typescript": "^5.5.4"
       },
@@ -1686,6 +1687,16 @@
       "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
       "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ=="
     },
+    "node_modules/@popperjs/core": {
+      "version": "2.11.8",
+      "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+      "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/popperjs"
+      }
+    },
     "node_modules/@rollup/rollup-android-arm-eabi": {
       "version": "4.13.0",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
@@ -6737,6 +6748,15 @@
         "entities": "^4.4.0"
       }
     },
+    "node_modules/tippy.js": {
+      "version": "6.3.7",
+      "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
+      "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@popperjs/core": "^2.9.0"
+      }
+    },
     "node_modules/to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
diff --git a/package.json b/package.json
index 493010b..4255bd0 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
     "sanitize-html": "^2.13.0",
     "tailwindcss": "^3.4.9",
     "tiny-decode": "^0.1.3",
+    "tippy.js": "^6.3.7",
     "toml": "^3.0.0",
     "typescript": "^5.5.4"
   },
diff --git a/src/content/tag-categories/9-type-of-content.yaml b/src/content/tag-categories/9-type-of-content.yaml
index 1c12c00..83677fe 100644
--- a/src/content/tag-categories/9-type-of-content.yaml
+++ b/src/content/tag-categories/9-type-of-content.yaml
@@ -6,6 +6,6 @@ tags:
   - name: commission
     description: Stories made as part of a commission to someone else.
   - name: { en: flash fiction, tok: lipu lili }
-    description: Short-format stories of no more than 2,500 words.
+    description: Short-format stories with no more than 2,500 words.
   - name: toki pona
     description: Stories written in toki pona, the language of good.
diff --git a/src/layouts/GalleryLayout.astro b/src/layouts/GalleryLayout.astro
index 6d90491..ab1a9b2 100644
--- a/src/layouts/GalleryLayout.astro
+++ b/src/layouts/GalleryLayout.astro
@@ -143,6 +143,7 @@ const isCurrentRoute = (path: string) =>
     </main>
   </div>
 </BaseLayout>
+
 <style>
   nav ul li a,
   nav ul li button {
@@ -159,3 +160,17 @@ const isCurrentRoute = (path: string) =>
     height: 1ex;
   }
 </style>
+
+<script>
+  import tippy from "tippy.js";
+  import "tippy.js/dist/tippy.css";
+
+  const clipboardItems = document.querySelectorAll<HTMLElement>("[data-clipboard]");
+
+  tippy(clipboardItems, {
+    content: (el) => (el as HTMLElement).dataset.clipboard!,
+    theme: "bm",
+  });
+
+  clipboardItems.forEach((el) => el.removeAttribute("title"));
+</script>
diff --git a/src/layouts/PublishedContentLayout.astro b/src/layouts/PublishedContentLayout.astro
index edca29a..3480126 100644
--- a/src/layouts/PublishedContentLayout.astro
+++ b/src/layouts/PublishedContentLayout.astro
@@ -82,6 +82,10 @@ const tags = props.tags.map<{ id: string; name: string }>((tag) => {
 const thumbnail =
   props.thumbnail &&
   (await getImage({ src: props.thumbnail, width: props.thumbnailWidth, height: props.thumbnailHeight }));
+
+const returnTo = series
+  ? t(props.lang, "published_content/return_to_series", series.data.name)
+  : props.labelReturnTo.title;
 ---
 
 <BaseLayout pageTitle={props.title} lang={props.lang}>
@@ -115,39 +119,24 @@ const thumbnail =
       <div
         class="pointer-events-auto sticky top-6 flex rounded-full bg-white px-1 py-1 shadow-md dark:bg-black print:hidden"
       >
-        <a
-          href={series ? series.data.link : props.labelReturnTo.link}
-          class="text-link my-1 p-2"
-          aria-labelledby="label-return-to"
-        >
+        <a href={series ? series.data.link : props.labelReturnTo.link} class="text-link my-1 p-2" aria-label={returnTo}>
           <IconArrowBack width="1.25rem" height="1.25rem" />
-          <span class="sr-only select-none" id="label-return-to"
-            >{
-              series ? t(props.lang, "published_content/return_to_series", series.data.name) : props.labelReturnTo.title
-            }</span
-          >
         </a>
         <a
           href="#description"
           class="text-link my-1 border-l border-stone-300 p-2 dark:border-stone-700"
-          aria-labelledby="label-go-to-description"
+          aria-label={t(props.lang, "published_content/go_to_description")}
         >
           <IconCircleInfo width="1.25rem" height="1.25rem" />
-          <span class="sr-only select-none" id="label-go-to-description"
-            >{t(props.lang, "published_content/go_to_description")}</span
-          >
         </a>
         <button
           data-dark-mode
           style={{ display: "none" }}
           class="text-link my-1 border-l border-stone-300 p-2 dark:border-stone-700"
-          aria-labelledby="label-toggle-dark-mode"
+          aria-label={t(props.lang, "published_content/toggle_dark_mode")}
         >
           <IconSun width="1.25rem" height="1.25rem" class="hidden dark:block" />
           <IconMoon width="1.25rem" height="1.25rem" class="block dark:hidden" />
-          <span class="sr-only select-none" id="label-toggle-dark-mode"
-            >{t(props.lang, "published_content/toggle_dark_mode")}</span
-          >
         </button>
       </div>
     </div>
@@ -195,15 +184,15 @@ const thumbnail =
       }
       <h1
         id="section-title"
-        class="p-name px-1 pt-4 font-serif text-4xl font-semibold text-stone-800 dark:text-stone-100"
+        class="p-name px-1 pt-4 font-serif text-4xl font-medium text-stone-800 dark:text-stone-200"
         aria-label={props.labelTitleSection}
       >
         {props.title}
       </h1>
-      <hr class="mb-3 ml-[2px] mt-2 h-[4px] w-1/2 rounded-sm bg-stone-800 dark:bg-stone-100" />
+      <hr class="mb-3 ml-[2px] mt-2 h-[4px] w-1/2 rounded-sm bg-stone-800 dark:bg-stone-200" />
       <section
         id="section-information"
-        class="p-summary mt-1 space-y-2 px-2 font-serif font-light italic text-stone-600 dark:text-stone-200"
+        class="p-summary mt-1 space-y-2 px-2 font-serif font-light italic text-stone-700 dark:text-stone-300"
         aria-label={props.labelInformationSection}
       >
         <slot name="section-information" />
@@ -402,3 +391,14 @@ const thumbnail =
     </div>
   </div>
 </BaseLayout>
+
+<script>
+  import tippy from "tippy.js";
+  import "tippy.js/dist/tippy.css";
+
+  tippy("#toolbox-buttons :is(a, button)[aria-label]", {
+    content: (el) => el.getAttribute("aria-label")!,
+    placement: "bottom",
+    theme: "bm",
+  });
+</script>
diff --git a/src/pages/games.astro b/src/pages/games.astro
index 39c0529..aec365e 100644
--- a/src/pages/games.astro
+++ b/src/pages/games.astro
@@ -30,6 +30,7 @@ const games = await Promise.all(
             class="u-url text-link hover:underline focus:underline"
             href={`/games/${game.slug}`}
             title={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
+            data-clipboard={t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
           >
             {game.data.thumbnail ? (
               <div class="flex aspect-[630/500] max-w-[288px] justify-center">
diff --git a/src/pages/index.astro b/src/pages/index.astro
index a22865d..b3733c6 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -102,7 +102,12 @@ const latestItems: LatestItemsEntry[] = await Promise.all(
       {
         latestItems.map((entry) => (
           <li class="h-entry break-inside-avoid" lang={entry.lang}>
-            <a class="u-url text-link hover:underline focus:underline" href={entry.href} title={entry.altText}>
+            <a
+              class="u-url text-link hover:underline focus:underline"
+              href={entry.href}
+              title={entry.altText}
+              data-clipboard={entry.altText}
+            >
               {entry.thumbnail ? (
                 <div class="flex aspect-square max-w-[192px] justify-center">
                   <Image
diff --git a/src/pages/stories/[page].astro b/src/pages/stories/[page].astro
index 618b49c..493fb12 100644
--- a/src/pages/stories/[page].astro
+++ b/src/pages/stories/[page].astro
@@ -88,6 +88,7 @@ const totalPages = Math.ceil(page.total / page.size);
             class="u-url text-link hover:underline focus:underline"
             href={`/stories/${story.slug}`}
             title={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
+            data-clipboard={t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
           >
             {story.data.thumbnail ? (
               <div class="flex aspect-square max-w-[192px] justify-center">
diff --git a/src/pages/tags.astro b/src/pages/tags.astro
index 58a2c89..8aff2ce 100644
--- a/src/pages/tags.astro
+++ b/src/pages/tags.astro
@@ -102,6 +102,7 @@ if (uncategorizedTagsSet.size > 0) {
                   class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm hover:underline focus:underline dark:bg-bm-600 dark:text-white"
                   href={`/tags/${id}`}
                   title={description}
+                  data-clipboard={description}
                 >
                   {name}
                 </a>
diff --git a/src/styles/base.css b/src/styles/base.css
index 1676347..4586750 100644
--- a/src/styles/base.css
+++ b/src/styles/base.css
@@ -4,19 +4,19 @@
 
 /* Tippy tooltips */
 .tippy-box[data-theme~="bm"] {
-  @apply bg-stone-800 font-sans text-sm text-stone-50 dark:bg-zinc-900 dark:text-zinc-100;
+  @apply bg-stone-900 font-sans text-sm text-stone-50 dark:bg-zinc-700 dark:text-zinc-50;
 }
 .tippy-box[data-theme~="bm"][data-placement^="top"] > .tippy-arrow::before {
-  @apply border-t-stone-800 dark:border-t-zinc-900;
+  @apply border-t-stone-900 dark:border-t-zinc-700;
 }
 .tippy-box[data-theme~="bm"][data-placement^="bottom"] > .tippy-arrow::before {
-  @apply border-b-stone-800 dark:border-b-zinc-900;
+  @apply border-b-stone-900 dark:border-b-zinc-700;
 }
 .tippy-box[data-theme~="bm"][data-placement^="left"] > .tippy-arrow::before {
-  @apply border-l-stone-800 dark:border-l-zinc-900;
+  @apply border-l-stone-900 dark:border-l-zinc-700;
 }
 .tippy-box[data-theme~="bm"][data-placement^="right"] > .tippy-arrow::before {
-  @apply border-r-stone-800 dark:border-r-zinc-900;
+  @apply border-r-stone-900 dark:border-r-zinc-700;
 }
 
 @layer components {
diff --git a/tailwind.config.mjs b/tailwind.config.mjs
index 9724321..83c4dbf 100644
--- a/tailwind.config.mjs
+++ b/tailwind.config.mjs
@@ -70,5 +70,5 @@ export default {
       }),
     },
   },
-  plugins: [require("@tailwindcss/typography")],
+  plugins: [typography],
 };