MessagePopover: Overflow Arrows
This commit is contained in:
parent
aa89a8cdd1
commit
0979c5c82c
4 changed files with 170 additions and 1 deletions
60
src/api/MessagePopover.css
Normal file
60
src/api/MessagePopover.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue