docs(HeaderBarAPI): add some comment and orgnaize code structure (#552)
* feat(HeaderBarAPI): ability to add buttons to ChannelToolbar * cleanup, organization and some comment cuz why not * t * fix: add ref prop to HeaderBarButtonProps * fix: remove duplicate channel toolbar patch * we actually dont need this lol
This commit is contained in:
parent
9d94430dfc
commit
feeb522dff
2 changed files with 140 additions and 65 deletions
|
|
@ -8,80 +8,153 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { useEffect, useState } from "@webpack/common";
|
||||
import type { ComponentType, JSX, MouseEventHandler, ReactNode, RefObject } from "react";
|
||||
import type { ComponentType, JSX, MouseEventHandler, ReactNode } from "react";
|
||||
|
||||
const logger = new Logger("HeaderBarAPI");
|
||||
|
||||
export type HeaderBarButtonFactory = () => JSX.Element | null;
|
||||
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"') as ComponentType<HeaderBarButtonProps>;
|
||||
|
||||
export type HeaderBarButtonLocation = "headerbar" | "channeltoolbar";
|
||||
|
||||
export interface HeaderBarButtonData {
|
||||
render: HeaderBarButtonFactory;
|
||||
export interface HeaderBarButtonProps {
|
||||
/** The icon component to render inside the button */
|
||||
icon: ComponentType<any>;
|
||||
priority?: number;
|
||||
location?: HeaderBarButtonLocation;
|
||||
/** Tooltip text shown on hover. Pass null to disable tooltip */
|
||||
tooltip: ReactNode;
|
||||
/** Called when the button is clicked */
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
/** Called when the button is right-clicked */
|
||||
onContextMenu?: MouseEventHandler<HTMLDivElement>;
|
||||
/** Additional CSS class names */
|
||||
className?: string;
|
||||
/** CSS class name for the icon element */
|
||||
iconClassName?: string;
|
||||
/** Tooltip position relative to the button */
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
/** Whether the button appears in a selected/active state */
|
||||
selected?: boolean;
|
||||
/** Whether the button is disabled */
|
||||
disabled?: boolean;
|
||||
/** Whether to show a notification badge */
|
||||
showBadge?: boolean;
|
||||
/** Position of the notification badge */
|
||||
badgePosition?: "top" | "bottom";
|
||||
/** Size of the icon in pixels */
|
||||
iconSize?: number;
|
||||
/** Ref to the button element */
|
||||
ref?: React.RefObject<any>;
|
||||
}
|
||||
|
||||
interface HeaderBarButton {
|
||||
export type HeaderBarButtonFactory = () => JSX.Element | null;
|
||||
|
||||
export interface HeaderBarButtonData {
|
||||
/** Function that renders the button component */
|
||||
render: HeaderBarButtonFactory;
|
||||
/** Icon component used for settings UI display */
|
||||
icon: ComponentType<any>;
|
||||
/** Higher priority buttons appear further right. Default: 0 */
|
||||
priority?: number;
|
||||
/** Where to render the button. Default: "headerbar" */
|
||||
location?: "headerbar" | "channeltoolbar";
|
||||
}
|
||||
|
||||
interface ButtonEntry {
|
||||
render: HeaderBarButtonFactory;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export const buttons = new Map<string, HeaderBarButton>();
|
||||
export const channelToolbarButtons = new Map<string, HeaderBarButton>();
|
||||
/**
|
||||
* Button component for the top header bar (title bar area).
|
||||
*
|
||||
* @example
|
||||
* <HeaderBarButton
|
||||
* icon={MyIcon}
|
||||
* tooltip="My Button"
|
||||
* onClick={() => console.log("clicked")}
|
||||
* />
|
||||
*/
|
||||
export const HeaderBarButton = HeaderBarIcon;
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
/**
|
||||
* Button component for the channel toolbar (below the search bar).
|
||||
* Automatically handles selected state styling.
|
||||
*
|
||||
* @example
|
||||
* <ChannelToolbarButton
|
||||
* icon={MyIcon}
|
||||
* tooltip={isOpen ? null : "My Button"}
|
||||
* onClick={() => setOpen(v => !v)}
|
||||
* selected={isOpen}
|
||||
* />
|
||||
*/
|
||||
export function ChannelToolbarButton(props: HeaderBarButtonProps) {
|
||||
return <HeaderBarIcon {...props} />;
|
||||
}
|
||||
|
||||
const headerBarButtons = new Map<string, ButtonEntry>();
|
||||
const channelToolbarButtons = new Map<string, ButtonEntry>();
|
||||
|
||||
const headerBarListeners = new Set<() => void>();
|
||||
const channelToolbarListeners = new Set<() => void>();
|
||||
|
||||
export interface HeaderBarButtonProps {
|
||||
icon: ComponentType<any>;
|
||||
tooltip: ReactNode;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
onContextMenu?: MouseEventHandler<HTMLDivElement>;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
showBadge?: boolean;
|
||||
badgePosition?: "top" | "bottom";
|
||||
iconSize?: number;
|
||||
ref?: RefObject<any>;
|
||||
}
|
||||
|
||||
export const HeaderBarButton = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"') as ComponentType<HeaderBarButtonProps>;
|
||||
|
||||
export const ChannelToolbarButton = HeaderBarButton;
|
||||
|
||||
/**
|
||||
* @param id Unique identifier for the button.
|
||||
* @param render Function that renders the button component.
|
||||
* @param priority Higher priority = more to the right. Default is 0.
|
||||
* Adds a button to the header bar (title bar area).
|
||||
*
|
||||
* @param id - Unique identifier for the button (e.g., "my-plugin-button")
|
||||
* @param render - Function that returns the button JSX
|
||||
* @param priority - Higher values appear further right. Default: 0
|
||||
*
|
||||
* @example
|
||||
* addHeaderBarButton("my-button", () => (
|
||||
* <HeaderBarButton
|
||||
* icon={MyIcon}
|
||||
* tooltip="My Button"
|
||||
* onClick={handleClick}
|
||||
* />
|
||||
* ));
|
||||
*/
|
||||
export function addHeaderBarButton(id: string, render: HeaderBarButtonFactory, priority = 0) {
|
||||
buttons.set(id, { render, priority });
|
||||
listeners.forEach(l => l());
|
||||
}
|
||||
|
||||
export function removeHeaderBarButton(id: string) {
|
||||
buttons.delete(id);
|
||||
listeners.forEach(l => l());
|
||||
headerBarButtons.set(id, { render, priority });
|
||||
headerBarListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id Unique identifier for the button.
|
||||
* @param render Function that renders the button component.
|
||||
* @param priority Higher priority = more to the right. Default is 0.
|
||||
* Removes a button from the header bar.
|
||||
*
|
||||
* @param id - The identifier used when adding the button
|
||||
*/
|
||||
export function removeHeaderBarButton(id: string) {
|
||||
headerBarButtons.delete(id);
|
||||
headerBarListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a button to the channel toolbar (below the search bar, next to pins/members).
|
||||
*
|
||||
* @param id - Unique identifier for the button (e.g., "my-plugin-toolbar")
|
||||
* @param render - Function that returns the button JSX
|
||||
* @param priority - Higher values appear further right. Default: 0
|
||||
*
|
||||
* @example
|
||||
* addChannelToolbarButton("my-toolbar", () => (
|
||||
* <ChannelToolbarButton
|
||||
* icon={MyIcon}
|
||||
* tooltip="My Button"
|
||||
* onClick={handleClick}
|
||||
* />
|
||||
* ));
|
||||
*/
|
||||
export function addChannelToolbarButton(id: string, render: HeaderBarButtonFactory, priority = 0) {
|
||||
channelToolbarButtons.set(id, { render, priority });
|
||||
channelToolbarListeners.forEach(l => l());
|
||||
channelToolbarListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a button from the channel toolbar.
|
||||
*
|
||||
* @param id - The identifier used when adding the button
|
||||
*/
|
||||
export function removeChannelToolbarButton(id: string) {
|
||||
channelToolbarButtons.delete(id);
|
||||
channelToolbarListeners.forEach(l => l());
|
||||
channelToolbarListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
function HeaderBarButtons() {
|
||||
|
|
@ -89,17 +162,17 @@ function HeaderBarButtons() {
|
|||
|
||||
useEffect(() => {
|
||||
const listener = () => forceUpdate(n => n + 1);
|
||||
listeners.add(listener);
|
||||
return () => { listeners.delete(listener); };
|
||||
headerBarListeners.add(listener);
|
||||
return () => { headerBarListeners.delete(listener); };
|
||||
}, []);
|
||||
|
||||
const sorted = Array.from(buttons).sort(([, a], [, b]) => a.priority - b.priority);
|
||||
|
||||
return sorted.map(([id, { render: Button }]) => (
|
||||
<ErrorBoundary noop key={id} onError={e => logger.error(`Failed to render ${id}`, e.error)}>
|
||||
<Button />
|
||||
</ErrorBoundary>
|
||||
));
|
||||
return Array.from(headerBarButtons)
|
||||
.sort(([, a], [, b]) => a.priority - b.priority)
|
||||
.map(([id, { render: Button }]) => (
|
||||
<ErrorBoundary noop key={id} onError={e => logger.error(`Failed to render header bar button: ${id}`, e.error)}>
|
||||
<Button />
|
||||
</ErrorBoundary>
|
||||
));
|
||||
}
|
||||
|
||||
function ChannelToolbarButtons() {
|
||||
|
|
@ -111,19 +184,21 @@ function ChannelToolbarButtons() {
|
|||
return () => { channelToolbarListeners.delete(listener); };
|
||||
}, []);
|
||||
|
||||
const sorted = Array.from(channelToolbarButtons).sort(([, a], [, b]) => a.priority - b.priority);
|
||||
|
||||
return sorted.map(([id, { render: Button }]) => (
|
||||
<ErrorBoundary noop key={id} onError={e => logger.error(`Failed to render channel toolbar ${id}`, e.error)}>
|
||||
<Button />
|
||||
</ErrorBoundary>
|
||||
));
|
||||
return Array.from(channelToolbarButtons)
|
||||
.sort(([, a], [, b]) => a.priority - b.priority)
|
||||
.map(([id, { render: Button }]) => (
|
||||
<ErrorBoundary noop key={id} onError={e => logger.error(`Failed to render channel toolbar button: ${id}`, e.error)}>
|
||||
<Button />
|
||||
</ErrorBoundary>
|
||||
));
|
||||
}
|
||||
|
||||
export function _addButtons() {
|
||||
/** @internal Injected by HeaderBarAPI patch (do NOT call directly) */
|
||||
export function _addHeaderBarButtons() {
|
||||
return [<HeaderBarButtons key="vc-header-bar-buttons" />];
|
||||
}
|
||||
|
||||
/** @internal Injected by HeaderBarAPI patch (do NOT call directly) */
|
||||
export function _addChannelToolbarButtons(toolbar: ReactNode[]) {
|
||||
toolbar.push(<ChannelToolbarButtons key="vc-channel-toolbar-buttons" />);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default definePlugin({
|
|||
find: '?"BACK_FORWARD_NAVIGATION":',
|
||||
replacement: {
|
||||
match: /(?<=\i\.Fragment,\{children:\[.{1,500}className:\i\}\))(?=\])/,
|
||||
replace: ",...Vencord.Api.HeaderBar._addButtons()"
|
||||
replace: ",...Vencord.Api.HeaderBar._addHeaderBarButtons()"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue