Kaynağa Gözat

feat: add ripple component

Sv443 11 ay önce
ebeveyn
işleme
a497f68ff6
3 değiştirilmiş dosya ile 74 ekleme ve 1 silme
  1. 30 0
      src/components/ripple.css
  2. 42 0
      src/components/ripple.ts
  3. 2 1
      src/interface.ts

+ 30 - 0
src/components/ripple.css

@@ -0,0 +1,30 @@
+:root html body .bytm-ripple {
+  position: relative;
+  width: var(--bytm-ripple-width, 100%);
+  height: var(--bytm-ripple-height, 100%);
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+.bytm-ripple-area {
+  position: absolute;
+  background: rgba(255, 255, 255, 0.7);
+  transform: translate(-50%, -50%);
+  pointer-events: none;
+  border-radius: 50%;
+  animation: bytm-scale-ripple 0.25s ease-in;
+}
+
+@keyframes bytm-scale-ripple {
+  0% {
+    width: 0;
+    height: 0;
+    opacity: 0.5;
+  }
+  100% {
+    width: 400px;
+    height: 400px;
+    opacity: 0;
+  }
+}

+ 42 - 0
src/components/ripple.ts

@@ -0,0 +1,42 @@
+import "./ripple.css";
+
+/**
+ * Creates an element with a ripple effect on click.
+ * @param clickEl If passed, this element will be modified to have the ripple effect. Otherwise, a new element will be created.
+ * @returns The passed element or the newly created element with the ripple effect.
+ */
+export function createRipple<TElem extends HTMLElement>(rippleElement: TElem): TElem;
+/**
+ * Creates an element with a ripple effect on click.
+ * @param clickEl If passed, this element will be modified to have the ripple effect. Otherwise, a new element will be created.
+ * @returns The passed element or the newly created element with the ripple effect.
+ */
+export function createRipple(rippleElement: undefined): HTMLDivElement;
+/**
+ * Creates an element with a ripple effect on click.
+ * @param clickEl If passed, this element will be modified to have the ripple effect. Otherwise, a new element will be created.
+ * @returns The passed element or the newly created element with the ripple effect.
+ */
+export function createRipple<TElem extends HTMLElement>(rippleElement?: TElem) {
+  const rippleEl = rippleElement ?? document.createElement("div");
+  rippleEl.classList.add("bytm-ripple");
+
+  rippleEl.addEventListener("click", (e) => {
+    const x = (e as MouseEvent).clientX - (e.target as HTMLElement)?.offsetLeft ?? 0;
+    const y = (e as MouseEvent).clientY - (e.target as HTMLElement)?.offsetTop ?? 0;
+
+    const rippleAreaEl = document.createElement("span");
+    rippleAreaEl.classList.add("bytm-ripple-area");
+    rippleAreaEl.style.left = x + "px";
+    rippleAreaEl.style.top = y + "px";
+
+    if(rippleEl.firstChild)
+      rippleEl.insertBefore(rippleAreaEl, rippleEl.firstChild);
+    else
+      rippleEl.appendChild(rippleAreaEl);
+
+    // setTimeout(() => rippleAreaEl.remove(), 250);
+  });
+
+  return rippleEl;
+}

+ 2 - 1
src/interface.ts

@@ -8,7 +8,7 @@ import { getFeatures, setFeatures } from "./config";
 import { featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features";
 import { allSiteEvents, type SiteEventsMap } from "./siteEvents";
 import { LogLevel, type FeatureConfig, type FeatureInfo, type LyricsCacheEntry, type PluginDef, type PluginInfo, type PluginRegisterResult, type PluginDefResolvable, type PluginEventMap, type PluginItem, type BytmObject } from "./types";
-import { BytmDialog, createCircularBtn, createHotkeyInput, createToggleInput, showIconToast, showToast } from "./components";
+import { BytmDialog, createCircularBtn, createHotkeyInput, createRipple, createToggleInput, showIconToast, showToast } from "./components";
 
 const { getUnsafeWindow, randomId } = UserUtils;
 
@@ -134,6 +134,7 @@ const globalFuncs = {
   // TODO: document
   showToast,
   showIconToast,
+  createRipple,
 };
 
 /** Initializes the BYTM interface */