MessagePopover: Overflow Arrows

This commit is contained in:
thororen 2026-09-23 19:43:05 -04:00
parent aa89a8cdd1
commit 0979c5c82c
No known key found for this signature in database
4 changed files with 170 additions and 1 deletions

View file

@ -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;
}
}

View file

@ -16,16 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "./MessagePopover.css";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { ChevronSmallLeftIcon, ChevronSmallRightIcon } from "@components/Icons";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc";
import { IconComponent } from "@utils/types"; import { IconComponent } from "@utils/types";
import { Channel, Message } from "@vencord/discord-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"; import { useSettings } from "./Settings";
const logger = new Logger("MessagePopover"); const logger = new Logger("MessagePopover");
const ToolbarClasses = findCssClassesLazy("wrapper", "button", "selected", "separator", "disabled", "dangerous");
const HoverBarClasses = findCssClassesLazy("popover", "hoverBarButton", "icon", "buttonContent");
export interface MessagePopoverButtonItem { export interface MessagePopoverButtonItem {
key?: string, key?: string,
label: string, label: string,
@ -91,6 +100,64 @@ function VencordPopoverButtons(props: { Component: React.ComponentType<MessagePo
return <>{elements}</>; return <>{elements}</>;
} }
function OverflowArrow({ direction, onClick }: { direction: "left" | "right", onClick(): void; }) {
const Icon = direction === "left" ? ChevronSmallLeftIcon : ChevronSmallRightIcon;
return (
<div className={`vc-popover-overflow-arrow vc-popover-overflow-arrow-${direction}`}>
<Clickable
className={classes(ToolbarClasses.button, HoverBarClasses.hoverBarButton)}
onClick={onClick}
aria-label={direction === "left" ? "Scroll left" : "Scroll right"}
>
<Icon className={HoverBarClasses.icon} />
</Clickable>
</div>
);
}
function PopoverOverflow({ children }: { children: ReactNode; }) {
const scrollerRef = useRef<HTMLDivElement>(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 (
<div ref={scrollerRef} className="vc-popover-overflow">
<div className="vc-popover-overflow-track">
<OverflowArrow direction="left" onClick={() => scroll(-1)} />
{children}
<OverflowArrow direction="right" onClick={() => scroll(1)} />
</div>
</div>
);
}
export function _wrapPopoverBar(children: ReactNode[]) {
return (
<ErrorBoundary fallback={() => <>{...children}</>}>
<PopoverOverflow>{...children}</PopoverOverflow>
</ErrorBoundary>
);
}
export function _buildPopoverElements( export function _buildPopoverElements(
Component: React.ComponentType<MessagePopoverButtonItem>, Component: React.ComponentType<MessagePopoverButtonItem>,
message: Message message: Message

View file

@ -791,6 +791,34 @@ export function ChevronSmallUpIcon(props: IconProps) {
); );
} }
export function ChevronSmallLeftIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M14.7 5.3a1 1 0 0 1 0 1.4L9.41 12l5.3 5.3a1 1 0 0 1-1.42 1.4l-6-6a1 1 0 0 1 0-1.4l6-6a1 1 0 0 1 1.42 0Z"
/>
</Icon>
);
}
export function ChevronSmallRightIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M9.3 5.3a1 1 0 0 0 0 1.4l5.29 5.3-5.3 5.3a1 1 0 1 0 1.42 1.4l6-6a1 1 0 0 0 0-1.4l-6-6a1 1 0 0 0-1.42 0Z"
/>
</Icon>
);
}
export function DownArrow(props: IconProps) { export function DownArrow(props: IconProps) {
return ( return (
<Icon <Icon

View file

@ -31,6 +31,20 @@ export default definePlugin({
replace: (_, ReactButton, ButtonComponent, showReactButton, message) => "" + replace: (_, ReactButton, ButtonComponent, showReactButton, message) => "" +
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,` `]}):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"'
}
} }
] ]
}); });