From 0979c5c82cbbcf721defcd18c050551aeb58d42b Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:43:05 -0400 Subject: [PATCH] MessagePopover: Overflow Arrows --- src/api/MessagePopover.css | 60 ++++++++++++++++++++++++++ src/api/MessagePopover.tsx | 69 +++++++++++++++++++++++++++++- src/components/Icons.tsx | 28 ++++++++++++ src/plugins/_api/messagePopover.ts | 14 ++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/api/MessagePopover.css diff --git a/src/api/MessagePopover.css b/src/api/MessagePopover.css new file mode 100644 index 000000000..e3848952a --- /dev/null +++ b/src/api/MessagePopover.css @@ -0,0 +1,60 @@ +.vc-message-popover-slot { + inset-inline-start: 0; +} + +.vc-message-popover { + max-width: 100%; + box-sizing: border-box; +} + +.vc-message-popover .vc-message-popover-bar { + display: flex; + max-width: 100%; +} + +.vc-popover-overflow { + min-width: 0; + overflow-x: auto; + scrollbar-width: none; + container-type: scroll-state; +} + +.vc-popover-overflow::-webkit-scrollbar { + display: none; +} + +.vc-popover-overflow-track { + display: flex; + align-items: center; + width: max-content; +} + +.vc-popover-overflow .vc-popover-overflow-arrow { + display: none; + position: sticky; + z-index: 1; + width: 24px; + background: var(--background-surface-high); +} + +.vc-popover-overflow .vc-popover-overflow-arrow-left { + left: 0; + margin-inline-end: -24px; +} + +.vc-popover-overflow .vc-popover-overflow-arrow-right { + right: 0; + margin-inline-start: -24px; +} + +@container scroll-state(scrollable: left) { + .vc-popover-overflow .vc-popover-overflow-arrow-left { + display: flex; + } +} + +@container scroll-state(scrollable: right) { + .vc-popover-overflow .vc-popover-overflow-arrow-right { + display: flex; + } +} \ No newline at end of file diff --git a/src/api/MessagePopover.tsx b/src/api/MessagePopover.tsx index d278d58de..c3d1fea86 100644 --- a/src/api/MessagePopover.tsx +++ b/src/api/MessagePopover.tsx @@ -16,16 +16,25 @@ * along with this program. If not, see . */ +import "./MessagePopover.css"; + import ErrorBoundary from "@components/ErrorBoundary"; +import { ChevronSmallLeftIcon, ChevronSmallRightIcon } from "@components/Icons"; import { Logger } from "@utils/Logger"; +import { classes } from "@utils/misc"; import { IconComponent } from "@utils/types"; import { Channel, Message } from "@vencord/discord-types"; -import type { ComponentType, MouseEventHandler } from "react"; +import { findCssClassesLazy } from "@webpack"; +import { Clickable, useLayoutEffect, useRef } from "@webpack/common"; +import type { ComponentType, MouseEventHandler, ReactNode } from "react"; import { useSettings } from "./Settings"; const logger = new Logger("MessagePopover"); +const ToolbarClasses = findCssClassesLazy("wrapper", "button", "selected", "separator", "disabled", "dangerous"); +const HoverBarClasses = findCssClassesLazy("popover", "hoverBarButton", "icon", "buttonContent"); + export interface MessagePopoverButtonItem { key?: string, label: string, @@ -91,6 +100,64 @@ function VencordPopoverButtons(props: { Component: React.ComponentType{elements}>; } +function OverflowArrow({ direction, onClick }: { direction: "left" | "right", onClick(): void; }) { + const Icon = direction === "left" ? ChevronSmallLeftIcon : ChevronSmallRightIcon; + + return ( + + + + + + ); +} + +function PopoverOverflow({ children }: { children: ReactNode; }) { + const scrollerRef = useRef(null); + + useLayoutEffect(() => { + const scroller = scrollerRef.current; + if (!scroller) return; + scroller.scrollLeft = scroller.scrollWidth; + + const onWheel = (e: WheelEvent) => { + if (scroller.scrollWidth <= scroller.clientWidth) return; + e.preventDefault(); + const delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY; + scroller.scrollLeft += e.deltaMode === WheelEvent.DOM_DELTA_LINE ? delta * 16 : delta; + }; + scroller.addEventListener("wheel", onWheel, { passive: false }); + return () => scroller.removeEventListener("wheel", onWheel); + }, []); + + const scroll = (direction: 1 | -1) => { + const scroller = scrollerRef.current; + scroller?.scrollBy({ left: direction * scroller.clientWidth * 0.75, behavior: "smooth" }); + }; + + return ( + + + scroll(-1)} /> + {children} + scroll(1)} /> + + + ); +} + +export function _wrapPopoverBar(children: ReactNode[]) { + return ( + <>{...children}>}> + {...children} + + ); +} + export function _buildPopoverElements( Component: React.ComponentType, message: Message diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 24c8e29b5..3e495db88 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -791,6 +791,34 @@ export function ChevronSmallUpIcon(props: IconProps) { ); } +export function ChevronSmallLeftIcon(props: IconProps) { + return ( + + + + ); +} + +export function ChevronSmallRightIcon(props: IconProps) { + return ( + + + + ); +} + export function DownArrow(props: IconProps) { return ( "" + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,` } + }, + { + find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", + replacement: { + match: /className:(\i\(\)\(\i\.className,.{0,80}?\)),(onClick:.{0,150}?children:\(0,\i\.jsxs?\)\(\i,\{className:)(\i\.innerClassName),children:(\[\i,\i\])/, + replace: 'className:"vc-message-popover "+$1,$2$3+" vc-message-popover-bar",children:Vencord.Api.MessagePopover._wrapPopoverBar($4)' + } + }, + { + find: 'role:"article",onMouseEnter', + replacement: { + match: /(?<=null!=(\i)\?\(0,\i\.jsxs?\)\("div",\{className:)\i\.\i(?=,children:\1\}\))/, + replace: '$&+" vc-message-popover-slot"' + } } ] });