Components: add plugin (dev) (#585)
* Components: add plugin (dev) * fix: rename alert component to notice and some fixes
This commit is contained in:
parent
819d43e059
commit
89f93e245e
69 changed files with 5416 additions and 94 deletions
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { filters, mapMangledModuleLazy } from "@webpack";
|
||||
import type { ComponentType, CSSProperties, ReactNode } from "react";
|
||||
|
||||
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', {
|
||||
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO", "ERROR"),
|
||||
HelpMessage: filters.byCode(".iconDiv")
|
||||
});
|
||||
|
||||
export interface AlertProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
icon?: ComponentType<{ className?: string; color?: string; }>;
|
||||
}
|
||||
|
||||
function Warning({ children, className, style, icon }: AlertProps) {
|
||||
return (
|
||||
<HelpMessage messageType={HelpMessageTypes.WARNING} className={className} style={style} icon={icon}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
function Info({ children, className, style, icon }: AlertProps) {
|
||||
return (
|
||||
<HelpMessage messageType={HelpMessageTypes.INFO} className={className} style={style} icon={icon}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
function Error({ children, className, style, icon }: AlertProps) {
|
||||
return (
|
||||
<HelpMessage messageType={HelpMessageTypes.ERROR} className={className} style={style} icon={icon}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
function Positive({ children, className, style, icon }: AlertProps) {
|
||||
return (
|
||||
<HelpMessage messageType={HelpMessageTypes.POSITIVE} className={className} style={style} icon={icon}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
export const Alert = {
|
||||
Warning,
|
||||
Info,
|
||||
Error,
|
||||
Positive
|
||||
};
|
||||
72
src/components/Notice.tsx
Normal file
72
src/components/Notice.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { filters, mapMangledModuleLazy } from "@webpack";
|
||||
import type { ComponentType, CSSProperties, ReactNode } from "react";
|
||||
|
||||
export type NoticeType = "warn" | "info" | "danger" | "positive" | "preview";
|
||||
|
||||
export const NoticeTypes: Record<"WARNING" | "INFO" | "ERROR" | "POSITIVE" | "PREVIEW", NoticeType> = {
|
||||
WARNING: "warn",
|
||||
INFO: "info",
|
||||
ERROR: "danger",
|
||||
POSITIVE: "positive",
|
||||
PREVIEW: "preview"
|
||||
};
|
||||
|
||||
const { HelpMessage } = mapMangledModuleLazy('POSITIVE="positive"', {
|
||||
HelpMessage: filters.byCode(".iconDiv")
|
||||
});
|
||||
|
||||
export interface NoticeProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
icon?: ComponentType<{ className?: string; color?: string; }>;
|
||||
action?: ReactNode;
|
||||
textColor?: string;
|
||||
textVariant?: string;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
function Warning({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
|
||||
return (
|
||||
<HelpMessage messageType={NoticeTypes.WARNING} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
function Info({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
|
||||
return (
|
||||
<HelpMessage messageType={NoticeTypes.INFO} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
function Error({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
|
||||
return (
|
||||
<HelpMessage messageType={NoticeTypes.ERROR} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
function Positive({ children, className, style, icon, action, textColor, textVariant, hidden }: NoticeProps) {
|
||||
return (
|
||||
<HelpMessage messageType={NoticeTypes.POSITIVE} className={className} style={style} icon={icon} action={action} textColor={textColor} textVariant={textVariant} hidden={hidden}>
|
||||
{children}
|
||||
</HelpMessage>
|
||||
);
|
||||
}
|
||||
|
||||
export const Notice = {
|
||||
Warning,
|
||||
Info,
|
||||
Error,
|
||||
Positive
|
||||
};
|
||||
|
|
@ -20,7 +20,6 @@ import "./themesStyles.css";
|
|||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Button } from "@components/Button";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
|
|
@ -29,6 +28,7 @@ import { FormSwitch } from "@components/FormSwitch";
|
|||
import { Heading } from "@components/Heading";
|
||||
import { CogWheel, DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PluginIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
||||
import { Link } from "@components/Link";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { AddonCard, openPluginModal, QuickAction, QuickActionCard, SettingsTab, wrapTab } from "@components/settings";
|
||||
import { OnlineThemeCard } from "@components/settings/OnlineThemeCard";
|
||||
|
|
@ -727,9 +727,9 @@ function ThemesTab() {
|
|||
}}
|
||||
/>
|
||||
|
||||
<Alert.Info className={Margins.bottom16} style={{ width: "100%" }}>
|
||||
<Notice.Info className={Margins.bottom16} style={{ width: "100%" }}>
|
||||
Looking for themes? Check out <Link href="https://betterdiscord.app/themes">BetterDiscord Themes</Link> or search on <Link href="https://github.com/search?q=discord+theme">GitHub</Link>. When downloading from BetterDiscord, click "Download" and place the .theme.css file into your themes folder.
|
||||
</Alert.Info>
|
||||
</Notice.Info>
|
||||
|
||||
<div className={cl("link-row")}>
|
||||
<TextInput
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export * from "./Alert";
|
||||
export * from "./BaseText";
|
||||
export * from "./Button";
|
||||
export * from "./Card";
|
||||
|
|
@ -21,6 +20,7 @@ export * from "./Heart";
|
|||
export * from "./Icons";
|
||||
export * from "./Link";
|
||||
export * from "./margins";
|
||||
export * from "./Notice";
|
||||
export * from "./Paragraph";
|
||||
export * from "./settings";
|
||||
export * from "./Span";
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@
|
|||
*/
|
||||
|
||||
import { downloadSettingsBackup, uploadSettingsBackup } from "@api/SettingsSync/offline";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Button } from "@components/Button";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
|
||||
import { Margins } from "@utils/margins";
|
||||
|
|
@ -34,9 +34,9 @@ function BackupAndRestoreTab() {
|
|||
Import and export your Equicord settings as a JSON file. This allows you to easily transfer your settings to another device, or recover them after reinstalling Equicord or Discord.
|
||||
</Paragraph>
|
||||
|
||||
<Alert.Warning className={Margins.bottom20}>
|
||||
<Notice.Warning className={Margins.bottom20}>
|
||||
Importing a settings file will overwrite your current settings. Make sure to export a backup first if you want to keep your current configuration.
|
||||
</Alert.Warning>
|
||||
</Notice.Warning>
|
||||
|
||||
<Heading>What's included in a backup</Heading>
|
||||
<Paragraph className={Margins.bottom20}>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
import { useSettings } from "@api/Settings";
|
||||
import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup";
|
||||
import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Button } from "@components/Button";
|
||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||
import { Divider } from "@components/Divider";
|
||||
|
|
@ -28,6 +27,7 @@ import { FormSwitch } from "@components/FormSwitch";
|
|||
import { Heading } from "@components/Heading";
|
||||
import { CloudDownloadIcon, CloudUploadIcon } from "@components/Icons";
|
||||
import { Link } from "@components/Link";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
|
||||
import { localStorage } from "@utils/localStorage";
|
||||
|
|
@ -97,11 +97,11 @@ function CloudTab() {
|
|||
Equicord's cloud integration allows you to sync your settings across multiple devices and Discord installations. Your data is securely stored and can be easily restored at any time.
|
||||
</Paragraph>
|
||||
|
||||
<Alert.Info className={Margins.bottom16}>
|
||||
<Notice.Info className={Margins.bottom16}>
|
||||
We use our own <Link href="https://github.com/Equicord/Equicloud">Equicloud backend</Link> with enhanced features.
|
||||
View our <Link href="https://equicord.org/cloud/policy">privacy policy</Link> to see what we store and how we use your data.
|
||||
Equicloud is BSD 3.0 licensed, so you can self-host if preferred.
|
||||
</Alert.Info>
|
||||
</Notice.Info>
|
||||
|
||||
<FormSwitch
|
||||
title="Enable Cloud Integration"
|
||||
|
|
@ -218,9 +218,9 @@ function CloudTab() {
|
|||
</Flex>
|
||||
|
||||
{!isAuthenticated && (
|
||||
<Alert.Warning className={Margins.top8}>
|
||||
<Notice.Warning className={Margins.top8}>
|
||||
Enable cloud integration above to use settings sync features.
|
||||
</Alert.Warning>
|
||||
</Notice.Warning>
|
||||
)}
|
||||
|
||||
<Divider className={Margins.top20} />
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import "./VencordTab.css";
|
|||
|
||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Button } from "@components/Button";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { FormSwitch } from "@components/FormSwitch";
|
||||
import { Heading } from "@components/Heading";
|
||||
import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/Icons";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings";
|
||||
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
|
||||
|
|
@ -224,7 +224,7 @@ function EquicordSettings() {
|
|||
<Paragraph className={Margins.bottom16}>
|
||||
Configure how Equicord behaves and integrates with Discord. These settings affect the Discord client's appearance and behavior.
|
||||
</Paragraph>
|
||||
<Alert.Info className={Margins.bottom20} style={{ width: "100%" }}>
|
||||
<Notice.Info className={Margins.bottom20} style={{ width: "100%" }}>
|
||||
You can customize where this settings section appears in Discord's settings menu by configuring the{" "}
|
||||
<a
|
||||
role="button"
|
||||
|
|
@ -233,7 +233,7 @@ function EquicordSettings() {
|
|||
>
|
||||
Settings Plugin
|
||||
</a>.
|
||||
</Alert.Info>
|
||||
</Notice.Info>
|
||||
|
||||
{Switches.filter((s): s is Exclude<typeof s, false> => !!s).map(
|
||||
s => (
|
||||
|
|
@ -246,9 +246,9 @@ function EquicordSettings() {
|
|||
s.warning.enabled ? (
|
||||
<>
|
||||
{s.description}
|
||||
<Alert.Warning className={Margins.top8} style={{ width: "100%" }}>
|
||||
<Notice.Warning className={Margins.top8} style={{ width: "100%" }}>
|
||||
{s.warning.message}
|
||||
</Alert.Warning>
|
||||
</Notice.Warning>
|
||||
</>
|
||||
) : (
|
||||
s.description
|
||||
|
|
|
|||
140
src/equicordplugins/components.dev/components/ComponentsTab.tsx
Normal file
140
src/equicordplugins/components.dev/components/ComponentsTab.tsx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "../styles.css";
|
||||
|
||||
import { Heading } from "@components/Heading";
|
||||
import { SettingsTab, wrapTab } from "@components/settings";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { TabBar, useState } from "@webpack/common";
|
||||
|
||||
import AnchorTab from "./tabs/AnchorTab";
|
||||
import AvatarTab from "./tabs/AvatarTab";
|
||||
import BadgeTab from "./tabs/BadgeTab";
|
||||
import ButtonsTab from "./tabs/ButtonsTab";
|
||||
import CalendarTab from "./tabs/CalendarTab";
|
||||
import CheckboxGroupTab from "./tabs/CheckboxGroupTab";
|
||||
import CheckboxTab from "./tabs/CheckboxTab";
|
||||
import ClickableTab from "./tabs/ClickableTab";
|
||||
import ColorPickerTab from "./tabs/ColorPickerTab";
|
||||
import ComboboxTab from "./tabs/ComboboxTab";
|
||||
import LayerModalTab from "./tabs/LayerModalTab";
|
||||
import ListboxTab from "./tabs/ListboxTab";
|
||||
import ModalTab from "./tabs/ModalTab";
|
||||
import NoticeTab from "./tabs/NoticeTab";
|
||||
import PaginatorTab from "./tabs/PaginatorTab";
|
||||
import PopoverTab from "./tabs/PopoverTab";
|
||||
import ProgressBarTab from "./tabs/ProgressBarTab";
|
||||
import RadioGroupTab from "./tabs/RadioGroupTab";
|
||||
import RichTooltipTab from "./tabs/RichTooltipTab";
|
||||
import SearchBarTab from "./tabs/SearchBarTab";
|
||||
import SelectTab from "./tabs/SelectTab";
|
||||
import SliderTab from "./tabs/SliderTab";
|
||||
import SpinnerTab from "./tabs/SpinnerTab";
|
||||
import SwitchTab from "./tabs/SwitchTab";
|
||||
import TabBarTab from "./tabs/TabBarTab";
|
||||
import TextAreaTab from "./tabs/TextAreaTab";
|
||||
import TextButtonTab from "./tabs/TextButtonTab";
|
||||
import TextInputTab from "./tabs/TextInputTab";
|
||||
import TooltipTab from "./tabs/TooltipTab";
|
||||
import TypographyTab from "./tabs/TypographyTab";
|
||||
|
||||
const TABS = [
|
||||
{ id: "anchor", label: "Anchor" },
|
||||
{ id: "avatar", label: "Avatar" },
|
||||
{ id: "badge", label: "Badge" },
|
||||
{ id: "buttons", label: "Buttons" },
|
||||
{ id: "calendar", label: "Calendar" },
|
||||
{ id: "checkbox", label: "Checkbox" },
|
||||
{ id: "checkboxgroup", label: "CheckboxGroup" },
|
||||
{ id: "clickable", label: "Clickable" },
|
||||
{ id: "colorpicker", label: "ColorPicker" },
|
||||
{ id: "combobox", label: "Combobox" },
|
||||
{ id: "layermodal", label: "LayerModal" },
|
||||
{ id: "listbox", label: "Listbox" },
|
||||
{ id: "modal", label: "Modal" },
|
||||
{ id: "notice", label: "Notice" },
|
||||
{ id: "paginator", label: "Paginator" },
|
||||
{ id: "popover", label: "Popover" },
|
||||
{ id: "progressbar", label: "ProgressBar" },
|
||||
{ id: "radiogroup", label: "RadioGroup" },
|
||||
{ id: "richtooltip", label: "RichTooltip" },
|
||||
{ id: "searchbar", label: "SearchBar" },
|
||||
{ id: "select", label: "Select" },
|
||||
{ id: "slider", label: "Slider" },
|
||||
{ id: "spinner", label: "Spinner" },
|
||||
{ id: "switch", label: "Switch" },
|
||||
{ id: "tabbar", label: "TabBar" },
|
||||
{ id: "textarea", label: "TextArea" },
|
||||
{ id: "textbutton", label: "TextButton" },
|
||||
{ id: "textinput", label: "TextInput" },
|
||||
{ id: "tooltip", label: "Tooltip" },
|
||||
{ id: "typography", label: "Typography" },
|
||||
].sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
type TabId = typeof TABS[number]["id"];
|
||||
|
||||
const TAB_COMPONENTS: Record<TabId, React.ComponentType> = {
|
||||
anchor: AnchorTab,
|
||||
avatar: AvatarTab,
|
||||
badge: BadgeTab,
|
||||
buttons: ButtonsTab,
|
||||
calendar: CalendarTab,
|
||||
checkbox: CheckboxTab,
|
||||
checkboxgroup: CheckboxGroupTab,
|
||||
clickable: ClickableTab,
|
||||
colorpicker: ColorPickerTab,
|
||||
combobox: ComboboxTab,
|
||||
layermodal: LayerModalTab,
|
||||
listbox: ListboxTab,
|
||||
modal: ModalTab,
|
||||
notice: NoticeTab,
|
||||
paginator: PaginatorTab,
|
||||
popover: PopoverTab,
|
||||
progressbar: ProgressBarTab,
|
||||
radiogroup: RadioGroupTab,
|
||||
richtooltip: RichTooltipTab,
|
||||
searchbar: SearchBarTab,
|
||||
select: SelectTab,
|
||||
slider: SliderTab,
|
||||
spinner: SpinnerTab,
|
||||
switch: SwitchTab,
|
||||
tabbar: TabBarTab,
|
||||
textarea: TextAreaTab,
|
||||
textbutton: TextButtonTab,
|
||||
textinput: TextInputTab,
|
||||
tooltip: TooltipTab,
|
||||
typography: TypographyTab,
|
||||
};
|
||||
|
||||
function ComponentsTab() {
|
||||
const [currentTab, setCurrentTab] = useState<TabId>("avatar");
|
||||
const TabComponent = TAB_COMPONENTS[currentTab];
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
<Heading className={Margins.bottom16}>Components</Heading>
|
||||
<TabBar
|
||||
type="top"
|
||||
look="brand"
|
||||
selectedItem={currentTab}
|
||||
onItemSelect={setCurrentTab}
|
||||
className="vc-compfinder-tabbar"
|
||||
>
|
||||
{TABS.map(tab => (
|
||||
<TabBar.Item key={tab.id} id={tab.id} className="vc-compfinder-tab">
|
||||
{tab.label}
|
||||
</TabBar.Item>
|
||||
))}
|
||||
</TabBar>
|
||||
<div className="vc-compfinder-content">
|
||||
<TabComponent />
|
||||
</div>
|
||||
</SettingsTab>
|
||||
);
|
||||
}
|
||||
|
||||
export default wrapTab(ComponentsTab, "ComponentsTab");
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
|
||||
interface SectionWrapperProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function SectionWrapper({ title, children }: SectionWrapperProps) {
|
||||
return (
|
||||
<div className={classes("vc-compfinder-section-block", Margins.bottom16)}>
|
||||
<Heading tag="h3" className={Margins.bottom8}>{title}</Heading>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export function EquicordIcon({ className, size, color }: { className?: string; size?: string; color?: string; }) {
|
||||
const dimension = size === "xs" ? 12 : size === "sm" ? 16 : size === "lg" ? 24 : 20;
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 443 443" width={dimension} height={dimension}>
|
||||
<path fill={color || "currentColor"} d="M221.5.6C99.2.6,0,99.7,0,222.1s99.2,221.5,221.5,221.5,221.5-99.2,221.5-221.5S343.8.6,221.5.6ZM221.5,363.3c-78.3,0-141.8-63.5-141.8-141.8s63.5-141.8,141.8-141.8,141.8,63.5,141.8,141.8-63.5,141.8-141.8,141.8Z" />
|
||||
<path fill={color || "currentColor"} d="M438.3,175C421,90.1,354.7,23.1,269.9,5.1,145-21.3,33.1,57.6,6.2,169.5c-3.4,14.1,4.6,28.5,18.4,33.1l2.7.9c24.7,8.2,51.8-4.7,60.4-29.3,19.4-55.6,72.5-95.4,134.9-95,44.3.3,84.1,21.2,109.9,53.7l-63.3,29.5c-7.9-7-17.5-12.5-28.5-15.7-35.6-10.3-73.8,7.7-88.5,41.7-17.2,39.8,3.3,85.4,43.9,99.4,37.9,13,79.7-6.9,93.5-44.5,2.6-7.2,4.1-14.5,4.4-21.8l66-30.8c1.8,8.2,2.9,16.6,3.2,25.2,1.2,34-9.6,65.5-28.5,90.5-17.8,23.6-10.5,57.4,15.7,71.1l1.3.7c10.4,5.5,23.3,3.4,31.4-5.2,46.8-49.9,70.8-121.4,55.2-198Z" />
|
||||
<path fill={color || "currentColor"} transform="translate(319.7 -16.5) rotate(65)" d="M172.8,152.7h0c17.7,0,32,14.3,32,32v148h-64v-148c0-17.7,14.3-32,32-32Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
189
src/equicordplugins/components.dev/components/index.tsx
Normal file
189
src/equicordplugins/components.dev/components/index.tsx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { findByCodeLazy, findComponentByCodeLazy, findLazy } from "@webpack";
|
||||
|
||||
import {
|
||||
AnchorProps,
|
||||
AvatarProps,
|
||||
BadgeShapesType,
|
||||
CheckboxGroupOption,
|
||||
CircleBadgeProps,
|
||||
ClickableProps,
|
||||
ColorPickerProps,
|
||||
ColorPickerWithSwatchesProps,
|
||||
ColorSwatchProps,
|
||||
CustomColorButtonProps,
|
||||
DefaultColorButtonProps,
|
||||
GradientColor,
|
||||
IconBadgeProps,
|
||||
ListboxItem,
|
||||
ManaBaseRadioGroupProps,
|
||||
ManaButtonProps,
|
||||
ManaCalendarProps,
|
||||
ManaCheckboxGroupProps,
|
||||
ManaCheckboxProps,
|
||||
ManaComboboxProps,
|
||||
ManaDatePickerProps,
|
||||
ManaListboxProps,
|
||||
ManaPopoverProps,
|
||||
ManaRichTooltipProps,
|
||||
ManaSelectOption,
|
||||
ManaSelectProps,
|
||||
ManaSwitchProps,
|
||||
ManaTextAreaProps,
|
||||
ManaTextButtonProps,
|
||||
ManaTextInputProps,
|
||||
ManaTooltipProps,
|
||||
NoticeProps,
|
||||
NoticeType,
|
||||
NumberBadgeProps,
|
||||
PaginatorProps,
|
||||
PopoverAction,
|
||||
ProgressBarProps,
|
||||
RadioOption,
|
||||
SearchBarProps,
|
||||
SliderProps,
|
||||
SpinnerProps,
|
||||
StandaloneRadioIndicatorProps,
|
||||
TabBarComponent,
|
||||
TabBarHeaderProps,
|
||||
TabBarItemProps,
|
||||
TabBarProps,
|
||||
TabBarSeparatorProps,
|
||||
TextBadgeProps
|
||||
} from "../types";
|
||||
|
||||
export type {
|
||||
AnchorProps,
|
||||
AvatarProps,
|
||||
BadgeShapesType,
|
||||
CheckboxGroupOption,
|
||||
CircleBadgeProps,
|
||||
ColorPickerProps,
|
||||
ColorPickerWithSwatchesProps,
|
||||
ColorSwatchProps,
|
||||
CustomColorButtonProps,
|
||||
DefaultColorButtonProps,
|
||||
GradientColor,
|
||||
IconBadgeProps,
|
||||
ListboxItem,
|
||||
ManaBaseRadioGroupProps,
|
||||
ManaButtonProps,
|
||||
ManaCalendarProps,
|
||||
ManaCheckboxGroupProps,
|
||||
ManaCheckboxProps,
|
||||
ManaComboboxProps,
|
||||
ManaDatePickerProps,
|
||||
ManaListboxProps,
|
||||
ManaPopoverProps,
|
||||
ManaRichTooltipProps,
|
||||
ManaSelectOption,
|
||||
ManaSelectProps,
|
||||
ManaSwitchProps,
|
||||
ManaTextAreaProps,
|
||||
ManaTextButtonProps,
|
||||
ManaTextInputProps,
|
||||
ManaTooltipProps,
|
||||
NoticeProps,
|
||||
NoticeType,
|
||||
NumberBadgeProps,
|
||||
PaginatorProps,
|
||||
PopoverAction,
|
||||
ProgressBarProps,
|
||||
RadioOption,
|
||||
SearchBarProps,
|
||||
SliderProps,
|
||||
SpinnerProps,
|
||||
StandaloneRadioIndicatorProps,
|
||||
TabBarComponent,
|
||||
TabBarHeaderProps,
|
||||
TabBarItemProps,
|
||||
TabBarProps,
|
||||
TabBarSeparatorProps,
|
||||
TextBadgeProps
|
||||
};
|
||||
|
||||
export const Anchor = findComponentByCodeLazy("anchorUnderlineOnHover", "useDefaultUnderlineStyles") as React.ComponentType<AnchorProps>;
|
||||
|
||||
export const ManaButton = findComponentByCodeLazy('"data-mana-component":"button"') as React.ComponentType<ManaButtonProps>;
|
||||
export const ManaTextButton = findComponentByCodeLazy('"data-mana-component":"text-button"') as React.ComponentType<ManaTextButtonProps>;
|
||||
export const ManaSwitch = findComponentByCodeLazy('"data-mana-component":"switch"') as React.ComponentType<ManaSwitchProps>;
|
||||
export const ManaTextInput = findComponentByCodeLazy('"data-mana-component":"text-input"') as React.ComponentType<ManaTextInputProps>;
|
||||
export const ManaTextArea = findComponentByCodeLazy('"data-mana-component":"text-area"') as React.ComponentType<ManaTextAreaProps>;
|
||||
export const ManaCheckbox = findComponentByCodeLazy('"data-mana-component":"checkbox"') as React.ComponentType<ManaCheckboxProps>;
|
||||
export const ManaSelect = findComponentByCodeLazy('"data-mana-component":"select"') as React.ComponentType<ManaSelectProps>;
|
||||
export const ManaTooltip = findComponentByCodeLazy('"data-mana-component":"tooltip"') as React.ComponentType<ManaTooltipProps>;
|
||||
export const ManaCalendar = findComponentByCodeLazy('"data-mana-component":"calendar"') as React.ComponentType<ManaCalendarProps>;
|
||||
export const ManaDatePicker = findComponentByCodeLazy('"data-mana-component":"date-picker"') as React.ComponentType<ManaDatePickerProps>;
|
||||
export const ManaRichTooltip = findComponentByCodeLazy('"data-mana-component":"rich-tooltip"') as React.ComponentType<ManaRichTooltipProps>;
|
||||
export const ManaCombobox = findComponentByCodeLazy("itemToString", "multiSelect", "maxVisibleItems") as React.ComponentType<ManaComboboxProps>;
|
||||
export const ManaListbox = findComponentByCodeLazy('"data-mana-component":"listbox"') as React.ComponentType<ManaListboxProps>;
|
||||
export const ManaCheckboxGroup = findComponentByCodeLazy('"data-mana-component":"checkbox-group"') as React.ComponentType<ManaCheckboxGroupProps>;
|
||||
export const ManaBaseRadioGroup = findComponentByCodeLazy('"data-mana-component":"BaseRadioGroup"') as React.ComponentType<ManaBaseRadioGroupProps>;
|
||||
export const StandaloneRadioIndicator = findComponentByCodeLazy("standaloneRadioIndicator", "animateIn", "animateOut") as React.ComponentType<StandaloneRadioIndicatorProps>;
|
||||
export const ManaPopover = findComponentByCodeLazy('"data-mana-component":"popover"') as React.ComponentType<ManaPopoverProps>;
|
||||
|
||||
export const Slider = findComponentByCodeLazy("stickToMarkers", "onMarkerRender", "grabberClassName") as React.ComponentType<SliderProps>;
|
||||
export const Avatar = findComponentByCodeLazy("statusTooltip", "statusBackdropColor", "isSpeaking") as React.ComponentType<AvatarProps>;
|
||||
export const ProgressBar = findComponentByCodeLazy("progressContainer", "labelledBy", "aria-valuenow") as React.ComponentType<ProgressBarProps>;
|
||||
export const Spinner = findComponentByCodeLazy("spinningCircleSimple", "pulsingEllipsis", "wanderingCubes") as React.ComponentType<SpinnerProps>;
|
||||
export const SearchBar = findComponentByCodeLazy("#{intl::SEARCH}", "clearable", "autoComplete") as React.ComponentType<SearchBarProps>;
|
||||
export const Paginator2 = findComponentByCodeLazy("#{intl::BACK}", "#{intl::NEXT}", "renderPageWrapper") as React.ComponentType<PaginatorProps>;
|
||||
export const Notice = findComponentByCodeLazy("messageType", "iconDiv", "actionContainer") as React.ComponentType<NoticeProps>;
|
||||
export const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", "showEyeDropper") as React.ComponentType<ColorPickerProps>;
|
||||
|
||||
const ColorPickerModule = findLazy(m => {
|
||||
const values = Object.values(m);
|
||||
return values.length === 4 &&
|
||||
values.some(v => typeof v === "function" && v.toString?.().includes('id:"color-picker"')) &&
|
||||
values.some(v => typeof v === "function" && v.toString?.().includes("isDefault:!0"));
|
||||
});
|
||||
|
||||
function getColorPickerExport(check: (fn: Function) => boolean): React.ComponentType<any> {
|
||||
for (const value of Object.values(ColorPickerModule)) {
|
||||
if (typeof value === "function" && check(value)) return value as React.ComponentType<any>;
|
||||
}
|
||||
return (() => null) as React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export const ColorPickerWithSwatches: React.ComponentType<ColorPickerWithSwatchesProps> = proxyLazy(() =>
|
||||
getColorPickerExport(fn => fn.toString().includes('id:"color-picker"'))
|
||||
);
|
||||
export const ColorSwatch: React.ComponentType<ColorSwatchProps> = proxyLazy(() =>
|
||||
getColorPickerExport(fn => fn.toString().includes("isDefault") && fn.toString().includes("isCustom") && fn.toString().includes("colorPickerSwatch"))
|
||||
);
|
||||
export const DefaultColorButton: React.ComponentType<DefaultColorButtonProps> = proxyLazy(() =>
|
||||
getColorPickerExport(fn => fn.toString().includes("isDefault:!0") && fn.toString().includes("allowBlackCustomColor"))
|
||||
);
|
||||
export const CustomColorButton: React.ComponentType<CustomColorButtonProps> = proxyLazy(() =>
|
||||
getColorPickerExport(fn => fn.toString().includes("isCustom:!0") && fn.toString().includes("presets"))
|
||||
);
|
||||
|
||||
const BadgeModule = findLazy(m => {
|
||||
const values = Object.values(m);
|
||||
return values.length < 15 &&
|
||||
values.some(v => typeof v === "function" && v.toString?.().includes("numberBadge")) &&
|
||||
values.some(v => typeof v === "object" && (v as BadgeShapesType)?.ROUND && (v as BadgeShapesType)?.ROUND_LEFT);
|
||||
});
|
||||
|
||||
function getBadgeExport(check: (fn: Function) => boolean): React.ComponentType<any> {
|
||||
for (const value of Object.values(BadgeModule)) {
|
||||
if (typeof value === "function" && check(value)) return value as React.ComponentType<any>;
|
||||
}
|
||||
return (() => null) as React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export const NumberBadge: React.ComponentType<NumberBadgeProps> = proxyLazy(() => getBadgeExport(fn => fn.toString().includes("numberBadge")));
|
||||
export const TextBadge: React.ComponentType<TextBadgeProps> = proxyLazy(() => getBadgeExport(fn => fn.toString().includes("textBadge")));
|
||||
export const IconBadge: React.ComponentType<IconBadgeProps> = proxyLazy(() => getBadgeExport(fn => fn.toString().includes("iconBadge")));
|
||||
export const CircleBadge: React.ComponentType<CircleBadgeProps> = proxyLazy(() => getBadgeExport(fn => fn.toString().includes("disableColor") && !fn.toString().includes("iconBadge") && !fn.toString().includes("textBadge") && !fn.toString().includes("numberBadge") && !fn.toString().includes("premiumBadge")));
|
||||
export const BadgeShapes = proxyLazy(() => Object.values(BadgeModule).find(v => typeof v === "object" && (v as BadgeShapesType)?.ROUND) as BadgeShapesType ?? { ROUND: "", ROUND_LEFT: "", ROUND_RIGHT: "", SQUARE: "" });
|
||||
|
||||
export const TabBar = findByCodeLazy("this.tabBarRef", "renderChildren") as TabBarComponent;
|
||||
|
||||
export const Clickable = findByCodeLazy("ignoreKeyPress", "renderNonInteractive") as React.ComponentType<ClickableProps>;
|
||||
112
src/equicordplugins/components.dev/components/tabs/AnchorTab.tsx
Normal file
112
src/equicordplugins/components.dev/components/tabs/AnchorTab.tsx
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { Anchor } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function AnchorTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic Anchor">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
A styled anchor/link component with underline on hover.
|
||||
</Paragraph>
|
||||
<Anchor href="https://discord.com">
|
||||
Discord Homepage
|
||||
</Anchor>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Without Underline Styles">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Disable the default underline-on-hover behavior.
|
||||
</Paragraph>
|
||||
<Anchor href="https://discord.com" useDefaultUnderlineStyles={false}>
|
||||
No underline on hover
|
||||
</Anchor>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Custom Target">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Open in same tab instead of new tab.
|
||||
</Paragraph>
|
||||
<Anchor href="https://discord.com" target="_self">
|
||||
Opens in same tab
|
||||
</Anchor>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Title Tooltip">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Hover to see the title tooltip.
|
||||
</Paragraph>
|
||||
<Anchor href="https://discord.com" title="Click to visit Discord">
|
||||
Hover for tooltip
|
||||
</Anchor>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Custom Styling">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Custom styles applied via style prop.
|
||||
</Paragraph>
|
||||
<Anchor
|
||||
href="https://discord.com"
|
||||
style={{ color: "var(--text-positive)", fontWeight: 600 }}
|
||||
>
|
||||
Green styled link
|
||||
</Anchor>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With onClick Handler">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Custom click handler (prevents default navigation).
|
||||
</Paragraph>
|
||||
<Anchor
|
||||
href="#"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
alert("Link clicked!");
|
||||
}}
|
||||
>
|
||||
Click me (shows alert)
|
||||
</Anchor>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Multiple Links">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Links in a sentence.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
Check out the <Anchor href="https://discord.com/terms">Terms of Service</Anchor> and{" "}
|
||||
<Anchor href="https://discord.com/privacy">Privacy Policy</Anchor> for more info.
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>Anchor</strong> - Styled link component (also known as MaskedLink)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• href?: string - Link URL</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick?: MouseEventHandler - Click handler</Paragraph>
|
||||
<Paragraph color="text-muted">• className?: string - Additional CSS classes</Paragraph>
|
||||
<Paragraph color="text-muted">• children?: ReactNode - Link content</Paragraph>
|
||||
<Paragraph color="text-muted">• rel?: string - Link relationship (auto-set for external)</Paragraph>
|
||||
<Paragraph color="text-muted">• target?: string - Link target (auto-set to _blank for external)</Paragraph>
|
||||
<Paragraph color="text-muted">• useDefaultUnderlineStyles?: boolean - Show underline on hover (default: true)</Paragraph>
|
||||
<Paragraph color="text-muted">• title?: string - Tooltip text on hover</Paragraph>
|
||||
<Paragraph color="text-muted">• style?: CSSProperties - Inline styles</Paragraph>
|
||||
<Paragraph color="text-muted">• focusProps?: object - Focus ring configuration</Paragraph>
|
||||
<Paragraph color="text-muted">• ref?: Ref - Forward ref to anchor element</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>Behavior</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• External links automatically get rel="noreferrer noopener" and target="_blank"</Paragraph>
|
||||
<Paragraph color="text-muted">• If no onClick is provided but href is set, uses Discord's default link interceptor</Paragraph>
|
||||
<Paragraph color="text-muted">• Untrusted external links show a warning modal before navigating</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
122
src/equicordplugins/components.dev/components/tabs/AvatarTab.tsx
Normal file
122
src/equicordplugins/components.dev/components/tabs/AvatarTab.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { Avatar } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SIZES = ["SIZE_16", "SIZE_20", "SIZE_24", "SIZE_32", "SIZE_40", "SIZE_48", "SIZE_56", "SIZE_80", "SIZE_120"] as const;
|
||||
|
||||
export default function AvatarTab() {
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
const avatarUrl = currentUser?.getAvatarURL?.(undefined, 128);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Avatar Sizes">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Avatar component from @webpack/common with various sizes.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid" style={{ alignItems: "flex-end" }}>
|
||||
{SIZES.map(size => (
|
||||
<div key={size} style={{ textAlign: "center" }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
size={size}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
{size.replace("SIZE_", "")}
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Status">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Avatar with status indicator.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
size="SIZE_40"
|
||||
status="online"
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>Online</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
size="SIZE_40"
|
||||
status="idle"
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>Idle</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
size="SIZE_40"
|
||||
status="dnd"
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>DND</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
size="SIZE_40"
|
||||
status="offline"
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>Offline</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Mobile Status">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Avatar with mobile indicator.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
size="SIZE_40"
|
||||
status="online"
|
||||
isMobile
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Speaking State">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Avatar with speaking ring indicator.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
size="SIZE_40"
|
||||
isSpeaking
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>Speaking</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• src - Avatar image URL</Paragraph>
|
||||
<Paragraph color="text-muted">• size - SIZE_16 to SIZE_120</Paragraph>
|
||||
<Paragraph color="text-muted">• status - online, idle, dnd, offline</Paragraph>
|
||||
<Paragraph color="text-muted">• isMobile - Show mobile indicator</Paragraph>
|
||||
<Paragraph color="text-muted">• isTyping - Show typing indicator</Paragraph>
|
||||
<Paragraph color="text-muted">• isSpeaking - Show speaking ring</Paragraph>
|
||||
<Paragraph color="text-muted">• statusTooltip - Show status on hover</Paragraph>
|
||||
<Paragraph color="text-muted">• aria-label - Accessibility label</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
106
src/equicordplugins/components.dev/components/tabs/BadgeTab.tsx
Normal file
106
src/equicordplugins/components.dev/components/tabs/BadgeTab.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { BadgeShapes, CircleBadge, IconBadge, NumberBadge, TextBadge } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function BadgeTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="NumberBadge">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Displays a count value. Automatically abbreviates large numbers.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<NumberBadge count={1} />
|
||||
<NumberBadge count={9} />
|
||||
<NumberBadge count={99} />
|
||||
<NumberBadge count={999} />
|
||||
<NumberBadge count={9999} />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="TextBadge">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Displays text content.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<TextBadge text="NEW" />
|
||||
<TextBadge text="BETA" />
|
||||
<TextBadge text="PRO" />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="IconBadge">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Displays an icon inside a badge.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<IconBadge icon={EquicordIcon} />
|
||||
<IconBadge icon={EquicordIcon} color="var(--status-positive)" />
|
||||
<IconBadge icon={EquicordIcon} color="var(--status-warning)" />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="CircleBadge">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
A simple circular badge indicator.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<CircleBadge />
|
||||
<CircleBadge color="var(--status-positive)" />
|
||||
<CircleBadge color="var(--status-warning)" />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Shapes">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Badges support different shapes via the shape prop.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<TextBadge text="ROUND" shape={BadgeShapes.ROUND} />
|
||||
<TextBadge text="LEFT" shape={BadgeShapes.ROUND_LEFT} />
|
||||
<TextBadge text="RIGHT" shape={BadgeShapes.ROUND_RIGHT} />
|
||||
<TextBadge text="SQUARE" shape={BadgeShapes.SQUARE} />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Colors">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Custom colors can be applied. Use disableColor for no background.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<NumberBadge count={5} color="var(--status-danger)" />
|
||||
<NumberBadge count={5} color="var(--status-positive)" />
|
||||
<NumberBadge count={5} color="var(--status-warning)" />
|
||||
<NumberBadge count={5} color="var(--brand-500)" />
|
||||
<NumberBadge count={5} disableColor />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
NumberBadge: count, maxDigits?, color?, disableColor?, shape?, className?, style?
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
TextBadge: text, color?, disableColor?, shape?, className?, style?
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
IconBadge: icon, color?, disableColor?, shape?, className?, style?
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
CircleBadge: color?, disableColor?, shape?, className?, style?
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
BadgeShapes: ROUND, ROUND_LEFT, ROUND_RIGHT, SQUARE
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const VARIANTS = ["primary", "secondary", "critical-primary", "critical-secondary", "overlay-primary", "overlay-secondary", "expressive"] as const;
|
||||
const SIZES = ["xs", "sm", "md"] as const;
|
||||
|
||||
export default function ButtonsTab() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Variants (size: md)">
|
||||
<div className="vc-compfinder-grid">
|
||||
{VARIANTS.map(variant => (
|
||||
<ManaButton key={variant} variant={variant} size="md" text={variant} />
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Sizes (variant: primary)">
|
||||
<div className="vc-compfinder-grid">
|
||||
{SIZES.map(size => (
|
||||
<ManaButton key={size} variant="primary" size={size} text={`Size: ${size}`} />
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="States">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton variant="primary" size="md" text="Disabled" disabled />
|
||||
<ManaButton
|
||||
variant="primary"
|
||||
size="md"
|
||||
text={loading ? "Loading..." : "Click to Load"}
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 2000);
|
||||
}}
|
||||
/>
|
||||
<ManaButton variant="primary" size="md" text="Rounded" rounded />
|
||||
<ManaButton variant="primary" size="md" text="Full Width" fullWidth />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="All Variants Disabled">
|
||||
<div className="vc-compfinder-grid">
|
||||
{VARIANTS.map(variant => (
|
||||
<ManaButton key={variant} variant={variant} size="md" text={variant} disabled />
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• text - Button label text</Paragraph>
|
||||
<Paragraph color="text-muted">• variant - primary, secondary, critical-primary, critical-secondary, overlay-primary, overlay-secondary, expressive</Paragraph>
|
||||
<Paragraph color="text-muted">• size - xs, sm, md</Paragraph>
|
||||
<Paragraph color="text-muted">• disabled - Disable button</Paragraph>
|
||||
<Paragraph color="text-muted">• loading - Show loading spinner</Paragraph>
|
||||
<Paragraph color="text-muted">• fullWidth - Expand to container width</Paragraph>
|
||||
<Paragraph color="text-muted">• rounded - Rounded corners</Paragraph>
|
||||
<Paragraph color="text-muted">• icon - Icon component</Paragraph>
|
||||
<Paragraph color="text-muted">• iconPosition - start or end</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick - Click handler</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaCalendar, ManaDatePicker } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function CalendarTab() {
|
||||
const [calendarValue, setCalendarValue] = useState<any>(null);
|
||||
const [datePickerValue, setDatePickerValue] = useState<any>(null);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Calendar">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Basic calendar component for date selection.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaCalendar
|
||||
value={calendarValue}
|
||||
onChange={setCalendarValue}
|
||||
aria-label="Select a date"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Calendar (Disabled)">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaCalendar
|
||||
value={calendarValue}
|
||||
onChange={setCalendarValue}
|
||||
disabled
|
||||
aria-label="Disabled calendar"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Calendar (Read Only)">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaCalendar
|
||||
value={calendarValue}
|
||||
onChange={setCalendarValue}
|
||||
readOnly
|
||||
aria-label="Read only calendar"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Date Picker">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Date picker with input field and calendar popover.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaDatePicker
|
||||
value={datePickerValue}
|
||||
onChange={setDatePickerValue}
|
||||
label="Select Date"
|
||||
description="Choose a date from the calendar"
|
||||
granularity="day"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Date Picker with Time">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Date picker with hour/minute granularity.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaDatePicker
|
||||
value={datePickerValue}
|
||||
onChange={setDatePickerValue}
|
||||
label="Select Date & Time"
|
||||
granularity="minute"
|
||||
hourCycle={24}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Date Picker States">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaDatePicker
|
||||
value={datePickerValue}
|
||||
onChange={setDatePickerValue}
|
||||
label="Disabled"
|
||||
disabled
|
||||
/>
|
||||
<ManaDatePicker
|
||||
value={datePickerValue}
|
||||
onChange={setDatePickerValue}
|
||||
label="Required"
|
||||
required
|
||||
/>
|
||||
<ManaDatePicker
|
||||
value={datePickerValue}
|
||||
onChange={setDatePickerValue}
|
||||
label="With Error"
|
||||
errorMessage="Please select a valid date"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>ManaCalendar</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• value: any - Selected date value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (value: any) => void - Selection callback
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable calendar
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• readOnly?: boolean - Read-only mode
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• aria-label?: string - Accessibility label
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>ManaDatePicker</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• value: any - Selected date value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (value: any) => void - Selection callback
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• label?: string - Input label
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• description?: string - Description text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• granularity?: "day" | "hour" | "minute" - Time precision
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• hourCycle?: 12 | 24 - Hour format
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable picker
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• required?: boolean - Mark as required
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• errorMessage?: string - Error text to display
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { CheckboxGroupOption, ManaCheckboxGroup } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const BASIC_OPTIONS: CheckboxGroupOption[] = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
{ value: "option3", label: "Option 3" },
|
||||
];
|
||||
|
||||
const OPTIONS_WITH_DESCRIPTIONS: CheckboxGroupOption[] = [
|
||||
{ value: "notifications", label: "Notifications", description: "Receive push notifications" },
|
||||
{ value: "sounds", label: "Sounds", description: "Play sounds for events" },
|
||||
{ value: "badges", label: "Badges", description: "Show unread badges" },
|
||||
];
|
||||
|
||||
const OPTIONS_WITH_ICONS: CheckboxGroupOption[] = [
|
||||
{ value: "item1", label: "Item with icon", description: "This option has an icon", leadingIcon: EquicordIcon },
|
||||
{ value: "item2", label: "Another with icon", leadingIcon: EquicordIcon },
|
||||
{ value: "item3", label: "Third with icon", leadingIcon: EquicordIcon },
|
||||
];
|
||||
|
||||
const OPTIONS_MIXED: CheckboxGroupOption[] = [
|
||||
{ value: "enabled", label: "Enabled option" },
|
||||
{ value: "disabled", label: "Disabled option", disabled: true },
|
||||
{ value: "another", label: "Another enabled" },
|
||||
];
|
||||
|
||||
export default function CheckboxGroupTab() {
|
||||
const [basicSelected, setBasicSelected] = useState<(string | number)[]>(["option1"]);
|
||||
const [descSelected, setDescSelected] = useState<(string | number)[]>(["notifications", "sounds"]);
|
||||
const [iconSelected, setIconSelected] = useState<(string | number)[]>(["item1"]);
|
||||
const [mixedSelected, setMixedSelected] = useState<(string | number)[]>(["enabled"]);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic">
|
||||
<ManaCheckboxGroup
|
||||
options={BASIC_OPTIONS}
|
||||
selectedValues={basicSelected}
|
||||
onChange={setBasicSelected}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Descriptions">
|
||||
<ManaCheckboxGroup
|
||||
options={OPTIONS_WITH_DESCRIPTIONS}
|
||||
selectedValues={descSelected}
|
||||
onChange={setDescSelected}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Leading Icons">
|
||||
<ManaCheckboxGroup
|
||||
options={OPTIONS_WITH_ICONS}
|
||||
selectedValues={iconSelected}
|
||||
onChange={setIconSelected}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Disabled Option">
|
||||
<ManaCheckboxGroup
|
||||
options={OPTIONS_MIXED}
|
||||
selectedValues={mixedSelected}
|
||||
onChange={setMixedSelected}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Entire Group Disabled">
|
||||
<ManaCheckboxGroup
|
||||
options={BASIC_OPTIONS}
|
||||
selectedValues={["option1", "option2"]}
|
||||
disabled
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>CheckboxGroup</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• options: CheckboxGroupOption[] - Array of options
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• selectedValues: (string | number)[] - Currently selected values
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (values: (string | number)[]) => void - Called when selection changes
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable entire group
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>CheckboxGroupOption</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• value: string | number - Option value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• label: string - Option label
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• description?: string - Optional description
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable individual option
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• leadingIcon?: React.ComponentType - Icon before checkbox
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaCheckbox } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function CheckboxTab() {
|
||||
const [basicChecked, setBasicChecked] = useState(true);
|
||||
const [basicUnchecked, setBasicUnchecked] = useState(false);
|
||||
|
||||
const [withDesc, setWithDesc] = useState(true);
|
||||
|
||||
const [labelPrimary, setLabelPrimary] = useState(true);
|
||||
const [labelSecondary, setLabelSecondary] = useState(false);
|
||||
|
||||
const [withIcon, setWithIcon] = useState(true);
|
||||
|
||||
const [usageSingle, setUsageSingle] = useState(true);
|
||||
const [usageIndicator, setUsageIndicator] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaCheckbox
|
||||
label="Checked"
|
||||
checked={basicChecked}
|
||||
onChange={setBasicChecked}
|
||||
/>
|
||||
<ManaCheckbox
|
||||
label="Unchecked"
|
||||
checked={basicUnchecked}
|
||||
onChange={setBasicUnchecked}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Description">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaCheckbox
|
||||
label="Option with description"
|
||||
description="This is a helpful description for the option."
|
||||
checked={withDesc}
|
||||
onChange={setWithDesc}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Label Types">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaCheckbox
|
||||
label="Primary label (default)"
|
||||
labelType="primary"
|
||||
checked={labelPrimary}
|
||||
onChange={setLabelPrimary}
|
||||
/>
|
||||
<ManaCheckbox
|
||||
label="Secondary label"
|
||||
labelType="secondary"
|
||||
checked={labelSecondary}
|
||||
onChange={setLabelSecondary}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Leading Icon">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaCheckbox
|
||||
label="With icon"
|
||||
description="This checkbox has a leading icon."
|
||||
leadingIcon={EquicordIcon}
|
||||
checked={withIcon}
|
||||
onChange={setWithIcon}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="States">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaCheckbox
|
||||
label="Disabled (checked)"
|
||||
checked={true}
|
||||
disabled
|
||||
/>
|
||||
<ManaCheckbox
|
||||
label="Disabled (unchecked)"
|
||||
checked={false}
|
||||
disabled
|
||||
/>
|
||||
<ManaCheckbox
|
||||
label="Display only"
|
||||
checked={true}
|
||||
displayOnly
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Usage Variants">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaCheckbox
|
||||
label="Single (default)"
|
||||
usageVariant="single"
|
||||
checked={usageSingle}
|
||||
onChange={setUsageSingle}
|
||||
/>
|
||||
<ManaCheckbox
|
||||
label="Indicator"
|
||||
usageVariant="indicator"
|
||||
checked={usageIndicator}
|
||||
onChange={setUsageIndicator}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• checked: boolean - Current checked state
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (checked: boolean) => void - Called when toggled
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• label?: string - Label text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• description?: string - Description below label
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• labelType?: "primary" | "secondary" - Label styling
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• leadingIcon?: React.ComponentType - Icon before checkbox
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• usageVariant?: "single" | "indicator" - Visual style
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable interaction
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• displayOnly?: boolean - Display without interaction
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { Clickable } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function ClickableTab() {
|
||||
const [clickCount, setClickCount] = useState(0);
|
||||
const [lastAction, setLastAction] = useState("None");
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic Clickable">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Default clickable element. Click count: {clickCount}
|
||||
</Paragraph>
|
||||
<Clickable
|
||||
onClick={() => setClickCount(c => c + 1)}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
Click me!
|
||||
</Clickable>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Custom Tag">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Clickable can render as different HTML elements using the tag prop.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<Clickable
|
||||
tag="div"
|
||||
onClick={() => setLastAction("Clicked div")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-tertiary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
tag="div"
|
||||
</Clickable>
|
||||
<Clickable
|
||||
tag="span"
|
||||
onClick={() => setLastAction("Clicked span")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-tertiary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
tag="span"
|
||||
</Clickable>
|
||||
<Clickable
|
||||
tag="button"
|
||||
onClick={() => setLastAction("Clicked button")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-tertiary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer",
|
||||
border: "none",
|
||||
color: "inherit"
|
||||
}}
|
||||
>
|
||||
tag="button"
|
||||
</Clickable>
|
||||
</div>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 8 }}>
|
||||
Last action: {lastAction}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Role and TabIndex">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Accessibility props for keyboard navigation.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<Clickable
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setLastAction("Role: button")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
role="button" tabIndex=0
|
||||
</Clickable>
|
||||
<Clickable
|
||||
role="link"
|
||||
tabIndex={0}
|
||||
onClick={() => setLastAction("Role: link")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
role="link" tabIndex=0
|
||||
</Clickable>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Ignore Key Press">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
When ignoreKeyPress is true, Enter/Space won't trigger onClick.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<Clickable
|
||||
tabIndex={0}
|
||||
onClick={() => setLastAction("Normal (keys work)")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
Normal (try Enter/Space)
|
||||
</Clickable>
|
||||
<Clickable
|
||||
tabIndex={0}
|
||||
ignoreKeyPress
|
||||
onClick={() => setLastAction("Ignored (keys blocked)")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
ignoreKeyPress (keys blocked)
|
||||
</Clickable>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Without onClick">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
When no onClick is provided, renders as non-interactive element.
|
||||
</Paragraph>
|
||||
<Clickable
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-modifier-accent)",
|
||||
borderRadius: 4
|
||||
}}
|
||||
>
|
||||
Non-interactive (no onClick)
|
||||
</Clickable>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Styled Examples">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Clickable with different visual styles.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<Clickable
|
||||
onClick={() => setLastAction("Card clicked")}
|
||||
style={{
|
||||
padding: 16,
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
borderRadius: 8,
|
||||
cursor: "pointer",
|
||||
width: 120,
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 24, marginBottom: 4 }}>📁</div>
|
||||
<div>Card Style</div>
|
||||
</Clickable>
|
||||
<Clickable
|
||||
onClick={() => setLastAction("Chip clicked")}
|
||||
style={{
|
||||
padding: "4px 12px",
|
||||
backgroundColor: "var(--brand-experiment)",
|
||||
borderRadius: 16,
|
||||
cursor: "pointer",
|
||||
color: "white"
|
||||
}}
|
||||
>
|
||||
Chip Style
|
||||
</Clickable>
|
||||
<Clickable
|
||||
onClick={() => setLastAction("List item clicked")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "var(--background-secondary)",
|
||||
cursor: "pointer",
|
||||
width: 200
|
||||
}}
|
||||
>
|
||||
List Item Style →
|
||||
</Clickable>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>Clickable</strong> - Interactive element wrapper
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• tag?: keyof JSX.IntrinsicElements - HTML element to render (default: div)</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick?: MouseEventHandler - Click handler</Paragraph>
|
||||
<Paragraph color="text-muted">• onKeyPress?: KeyboardEventHandler - Key press handler</Paragraph>
|
||||
<Paragraph color="text-muted">• focusProps?: object - Props for FocusLock wrapper</Paragraph>
|
||||
<Paragraph color="text-muted">• innerRef?: Ref - Ref to the inner element</Paragraph>
|
||||
<Paragraph color="text-muted">• role?: string - ARIA role attribute</Paragraph>
|
||||
<Paragraph color="text-muted">• tabIndex?: number - Tab index for keyboard navigation</Paragraph>
|
||||
<Paragraph color="text-muted">• ignoreKeyPress?: boolean - Disable Enter/Space triggering onClick</Paragraph>
|
||||
<Paragraph color="text-muted">• href?: string - Optional href (affects key handling)</Paragraph>
|
||||
<Paragraph color="text-muted">• className?: string - CSS class name</Paragraph>
|
||||
<Paragraph color="text-muted">• children?: ReactNode - Child elements</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ColorPicker, ColorPickerWithSwatches, ColorSwatch, CustomColorButton, DefaultColorButton } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const DEFAULT_COLOR = 0x5865F2;
|
||||
|
||||
const PRESET_COLORS = [
|
||||
0x1ABC9C, 0x2ECC71, 0x3498DB, 0x9B59B6, 0xE91E63,
|
||||
0xF1C40F, 0xE67E22, 0xE74C3C, 0x95A5A6, 0x607D8B,
|
||||
0x11806A, 0x1F8B4C, 0x206694, 0x71368A, 0xAD1457,
|
||||
0xC27C0E, 0xA84300, 0x992D22, 0x979C9F, 0x546E7A,
|
||||
];
|
||||
|
||||
function intToHex(color: number): string {
|
||||
return "#" + color.toString(16).padStart(6, "0");
|
||||
}
|
||||
|
||||
export default function ColorPickerTab() {
|
||||
const [basicColor, setBasicColor] = useState(DEFAULT_COLOR);
|
||||
const [suggestedColor, setSuggestedColor] = useState(0xE74C3C);
|
||||
const [swatchColor, setSwatchColor] = useState(0x3498DB);
|
||||
const [swatchSelected, setSwatchSelected] = useState(0x2ECC71);
|
||||
const [customSwatchColor, setCustomSwatchColor] = useState(0x9B59B6);
|
||||
const [defaultBtnColor, setDefaultBtnColor] = useState(DEFAULT_COLOR);
|
||||
const [customBtnColor, setCustomBtnColor] = useState(0xE91E63);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic ColorPicker">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Click the swatch to open a full color picker popout. Selected: {intToHex(basicColor)}
|
||||
</Paragraph>
|
||||
<ColorPicker
|
||||
color={basicColor}
|
||||
onChange={setBasicColor}
|
||||
showEyeDropper={false}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Eye Dropper">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Includes the eye dropper tool for picking colors from screen.
|
||||
</Paragraph>
|
||||
<ColorPicker
|
||||
color={basicColor}
|
||||
onChange={setBasicColor}
|
||||
showEyeDropper={true}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Suggested Colors">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Popout shows suggested color swatches. Selected: {intToHex(suggestedColor)}
|
||||
</Paragraph>
|
||||
<ColorPicker
|
||||
color={suggestedColor}
|
||||
onChange={setSuggestedColor}
|
||||
showEyeDropper={false}
|
||||
suggestedColors={[
|
||||
"#1ABC9C", "#2ECC71", "#3498DB", "#9B59B6", "#E91E63",
|
||||
"#F1C40F", "#E67E22", "#E74C3C", "#95A5A6", "#607D8B",
|
||||
]}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Disabled ColorPicker">
|
||||
<ColorPicker
|
||||
color={0x5865F2}
|
||||
onChange={() => { }}
|
||||
disabled
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ColorSwatch - Basic">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Individual color swatch buttons. Click to select.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
{PRESET_COLORS.slice(0, 10).map(color => (
|
||||
<ColorSwatch
|
||||
key={color}
|
||||
color={color}
|
||||
isSelected={swatchSelected === color}
|
||||
onClick={() => setSwatchSelected(color)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ColorSwatch - States">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Different swatch states: default, custom, selected, disabled, no color.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} />
|
||||
<Paragraph color="text-muted" size="xs">Normal</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} isSelected />
|
||||
<Paragraph color="text-muted" size="xs">Selected</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} isDefault />
|
||||
<Paragraph color="text-muted" size="xs">Default</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} isCustom />
|
||||
<Paragraph color="text-muted" size="xs">Custom</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} disabled />
|
||||
<Paragraph color="text-muted" size="xs">Disabled</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch />
|
||||
<Paragraph color="text-muted" size="xs">No Color</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ColorSwatch - Gradient">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Gradient swatches with customizable angle.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<ColorSwatch
|
||||
isGradient
|
||||
gradientStart={0xE91E63}
|
||||
gradientEnd={0x9B59B6}
|
||||
gradientDegrees={90}
|
||||
/>
|
||||
<ColorSwatch
|
||||
isGradient
|
||||
gradientStart={0x3498DB}
|
||||
gradientEnd={0x2ECC71}
|
||||
gradientDegrees={135}
|
||||
/>
|
||||
<ColorSwatch
|
||||
isGradient
|
||||
gradientStart={0xF1C40F}
|
||||
gradientEnd={0xE74C3C}
|
||||
gradientDegrees={180}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="DefaultColorButton">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
The "default" button used in ColorPickerWithSwatches. Shows checkmark when selected.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 16, alignItems: "center" }}>
|
||||
<DefaultColorButton
|
||||
color={DEFAULT_COLOR}
|
||||
value={defaultBtnColor}
|
||||
onChange={setDefaultBtnColor}
|
||||
/>
|
||||
<Paragraph color="text-muted">Selected: {defaultBtnColor === DEFAULT_COLOR ? "Yes" : "No"}</Paragraph>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="CustomColorButton">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
The "custom" button for custom color selection. Shows dropper icon.
|
||||
</Paragraph>
|
||||
<CustomColorButton
|
||||
customColor={customBtnColor}
|
||||
value={customBtnColor}
|
||||
presets={PRESET_COLORS}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ColorPickerWithSwatches">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Full swatch grid with preset colors. Selected: {intToHex(swatchColor)}
|
||||
</Paragraph>
|
||||
<ColorPickerWithSwatches
|
||||
defaultColor={DEFAULT_COLOR}
|
||||
colors={PRESET_COLORS}
|
||||
value={swatchColor}
|
||||
onChange={setSwatchColor}
|
||||
renderDefaultButton={() => null}
|
||||
renderCustomButton={() => null}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Default & Custom Buttons">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Full picker with default reset button and custom color picker. Selected: {intToHex(customSwatchColor)}
|
||||
</Paragraph>
|
||||
<ColorPickerWithSwatches
|
||||
defaultColor={DEFAULT_COLOR}
|
||||
customColor={customSwatchColor}
|
||||
colors={PRESET_COLORS}
|
||||
value={customSwatchColor}
|
||||
onChange={setCustomSwatchColor}
|
||||
renderDefaultButton={props => (
|
||||
<DefaultColorButton {...props} />
|
||||
)}
|
||||
renderCustomButton={props => (
|
||||
<ColorPicker
|
||||
color={props.customColor ?? DEFAULT_COLOR}
|
||||
onChange={setCustomSwatchColor}
|
||||
showEyeDropper={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>ColorPicker</strong> - Full color picker with popout
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• color: number - Current color as integer</Paragraph>
|
||||
<Paragraph color="text-muted">• onChange: (color: number) ={">"} void - Change callback</Paragraph>
|
||||
<Paragraph color="text-muted">• onClose?: () ={">"} void - Called when picker closes</Paragraph>
|
||||
<Paragraph color="text-muted">• suggestedColors?: string[] - Hex color strings for suggestions</Paragraph>
|
||||
<Paragraph color="text-muted">• disabled?: boolean - Disable interaction</Paragraph>
|
||||
<Paragraph color="text-muted">• label?: ReactNode - Optional label next to swatch</Paragraph>
|
||||
<Paragraph color="text-muted">• colorPickerMiddle?: ReactNode - Content in middle of popout</Paragraph>
|
||||
<Paragraph color="text-muted">• colorPickerFooter?: ReactNode - Content in footer of popout</Paragraph>
|
||||
<Paragraph color="text-muted">• showEyeDropper?: boolean - Show eye dropper tool</Paragraph>
|
||||
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>ColorSwatch</strong> - Individual color swatch button
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• color?: number - Color value (null for "no color")</Paragraph>
|
||||
<Paragraph color="text-muted">• isDefault?: boolean - Larger "default" style</Paragraph>
|
||||
<Paragraph color="text-muted">• isCustom?: boolean - Shows dropper icon</Paragraph>
|
||||
<Paragraph color="text-muted">• isSelected?: boolean - Shows checkmark</Paragraph>
|
||||
<Paragraph color="text-muted">• disabled?: boolean - Disable interaction</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick?: (color) ={">"} void - Click callback</Paragraph>
|
||||
<Paragraph color="text-muted">• isGradient?: boolean - Gradient mode</Paragraph>
|
||||
<Paragraph color="text-muted">• gradientStart/End?: number - Gradient colors</Paragraph>
|
||||
<Paragraph color="text-muted">• gradientDegrees?: number - Gradient angle (default 180)</Paragraph>
|
||||
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>ColorPickerWithSwatches</strong> - Swatch grid with render props
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• defaultColor: number - Default/reset color</Paragraph>
|
||||
<Paragraph color="text-muted">• customColor?: number - Current custom color</Paragraph>
|
||||
<Paragraph color="text-muted">• colors: number[] - Array of preset colors</Paragraph>
|
||||
<Paragraph color="text-muted">• value: number - Selected color</Paragraph>
|
||||
<Paragraph color="text-muted">• onChange: (color: number) ={">"} void - Change callback</Paragraph>
|
||||
<Paragraph color="text-muted">• renderDefaultButton?: (props) ={">"} ReactNode</Paragraph>
|
||||
<Paragraph color="text-muted">• renderCustomButton?: (props) ={">"} ReactNode</Paragraph>
|
||||
<Paragraph color="text-muted">• isGradient?: boolean - Gradient mode</Paragraph>
|
||||
<Paragraph color="text-muted">• gradientDegrees?: number - Gradient angle</Paragraph>
|
||||
<Paragraph color="text-muted">• allowBlackCustomColor?: boolean - Allow #000000</Paragraph>
|
||||
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>Color Conversion</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• Hex to int: parseInt(hex.replace("#", ""), 16)</Paragraph>
|
||||
<Paragraph color="text-muted">• Int to hex: "#" + color.toString(16).padStart(6, "0")</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton, ManaCombobox } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SAMPLE_ITEMS = [
|
||||
{ id: "1", label: "Apple" },
|
||||
{ id: "2", label: "Banana" },
|
||||
{ id: "3", label: "Cherry" },
|
||||
{ id: "4", label: "Date" },
|
||||
{ id: "5", label: "Elderberry" },
|
||||
{ id: "6", label: "Fig" },
|
||||
{ id: "7", label: "Grape" },
|
||||
{ id: "8", label: "Honeydew" },
|
||||
];
|
||||
|
||||
function ComboboxItem({ item, isSelected, onClick }: { item: { id: string; label: string; }; isSelected: boolean; onClick: () => void; }) {
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={{
|
||||
padding: "8px 12px",
|
||||
cursor: "pointer",
|
||||
background: isSelected ? "var(--background-modifier-selected)" : "transparent",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = "var(--background-modifier-hover)")}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = isSelected ? "var(--background-modifier-selected)" : "transparent")}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ComboboxTab() {
|
||||
const [singleValue, setSingleValue] = useState<string>("");
|
||||
const [multiValue, setMultiValue] = useState<string[]>([]);
|
||||
const [showDemo, setShowDemo] = useState(false);
|
||||
|
||||
const filterItems = (query: string) =>
|
||||
SAMPLE_ITEMS.filter(item =>
|
||||
item.label.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Combobox">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Searchable dropdown with render props pattern. Children is a function
|
||||
that receives the search query and returns filterable items.
|
||||
</Paragraph>
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={showDemo ? "Hide Demo" : "Show Demo"}
|
||||
onClick={() => setShowDemo(!showDemo)}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
{showDemo && (
|
||||
<>
|
||||
<SectionWrapper title="Single Selection">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Selected: {singleValue || "None"}
|
||||
</Paragraph>
|
||||
<div style={{ maxWidth: 300 }}>
|
||||
<ManaCombobox
|
||||
placeholder="Search fruits..."
|
||||
value={singleValue}
|
||||
onChange={v => setSingleValue(v as string)}
|
||||
aria-label="Select a fruit"
|
||||
maxVisibleItems={5}
|
||||
emptyStateHeader="No results"
|
||||
emptyStateText="Try a different search term"
|
||||
>
|
||||
{query => filterItems(query).map(item => (
|
||||
<ComboboxItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
isSelected={singleValue === item.id}
|
||||
onClick={() => setSingleValue(item.id)}
|
||||
/>
|
||||
))}
|
||||
</ManaCombobox>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Multi Selection">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Selected: {multiValue.length ? multiValue.join(", ") : "None"}
|
||||
</Paragraph>
|
||||
<div style={{ maxWidth: 300 }}>
|
||||
<ManaCombobox
|
||||
placeholder="Search fruits..."
|
||||
value={multiValue}
|
||||
onChange={v => setMultiValue(v as string[])}
|
||||
multiSelect
|
||||
aria-label="Select fruits"
|
||||
maxVisibleItems={5}
|
||||
>
|
||||
{query => filterItems(query).map(item => (
|
||||
<ComboboxItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
isSelected={multiValue.includes(item.id)}
|
||||
onClick={() => {
|
||||
if (multiValue.includes(item.id)) {
|
||||
setMultiValue(multiValue.filter(v => v !== item.id));
|
||||
} else {
|
||||
setMultiValue([...multiValue, item.id]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ManaCombobox>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
</>
|
||||
)}
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• placeholder - Input placeholder text</Paragraph>
|
||||
<Paragraph color="text-muted">• value - Selected value(s)</Paragraph>
|
||||
<Paragraph color="text-muted">• onChange - Selection change callback</Paragraph>
|
||||
<Paragraph color="text-muted">• multiSelect - Enable multiple selection</Paragraph>
|
||||
<Paragraph color="text-muted">• autoFocus - Focus input on mount</Paragraph>
|
||||
<Paragraph color="text-muted">• maxVisibleItems - Max items before scroll (default 5)</Paragraph>
|
||||
<Paragraph color="text-muted">• emptyStateHeader - Header when no results</Paragraph>
|
||||
<Paragraph color="text-muted">• emptyStateText - Text when no results</Paragraph>
|
||||
<Paragraph color="text-muted">• children - Function: (query: string) => ReactNode[]</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const LayerModal = findComponentByCodeLazy('"data-mana-component":"layer-modal"') as React.ComponentType<{
|
||||
transitionState: number;
|
||||
animationVariant?: "default" | string;
|
||||
returnRef?: React.Ref<HTMLDivElement>;
|
||||
"aria-label"?: string;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
}>;
|
||||
|
||||
const TransitionState = findByPropsLazy("ENTERING", "ENTERED", "EXITING") as {
|
||||
ENTERING: number;
|
||||
ENTERED: number;
|
||||
EXITING: number;
|
||||
EXITED: number;
|
||||
HIDDEN: number;
|
||||
};
|
||||
|
||||
function LayerModalDemo({ onClose }: { onClose: () => void; }) {
|
||||
return (
|
||||
<LayerModal
|
||||
transitionState={TransitionState.ENTERED}
|
||||
onClose={onClose}
|
||||
aria-label="Demo Layer Modal"
|
||||
>
|
||||
<div style={{ padding: 24, minWidth: 300 }}>
|
||||
<Heading tag="h2" style={{ marginBottom: 8 }}>Layer Modal</Heading>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 16 }}>
|
||||
This is a layer modal. It's used for layered/stacked modals
|
||||
with different animation behavior than regular modals.
|
||||
</Paragraph>
|
||||
<ManaButton variant="primary" text="Close" onClick={onClose} />
|
||||
</div>
|
||||
</LayerModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LayerModalTab() {
|
||||
const [showInline, setShowInline] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Layer Modal">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Layer modals are used for stacked/layered modal experiences.
|
||||
They have different animation variants and are typically used
|
||||
when you need to show a modal on top of another modal.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text="Open via openModal"
|
||||
onClick={() => openModal(props => (
|
||||
<LayerModalDemo onClose={props.onClose} />
|
||||
))}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Inline Preview">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Toggle to see the layer modal rendered inline (not recommended for production).
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={showInline ? "Hide Inline" : "Show Inline"}
|
||||
onClick={() => setShowInline(!showInline)}
|
||||
/>
|
||||
</div>
|
||||
{showInline && (
|
||||
<div style={{ marginTop: 16, position: "relative", minHeight: 200 }}>
|
||||
<LayerModal
|
||||
transitionState={TransitionState.ENTERED}
|
||||
onClose={() => setShowInline(false)}
|
||||
aria-label="Inline Demo"
|
||||
>
|
||||
<div style={{ padding: 16 }}>
|
||||
<Paragraph>Inline layer modal content</Paragraph>
|
||||
</div>
|
||||
</LayerModal>
|
||||
</div>
|
||||
)}
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>LayerModal</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• transitionState: number - Use TransitionState.ENTERED for visible
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onClose: () => void - Close handler
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• children: ReactNode - Modal content
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• animationVariant?: "default" | string - Animation style
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• returnRef?: React.Ref - Ref for the modal element
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• aria-label?: string - Accessibility label
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>TransitionState</strong> - findByPropsLazy("ENTERING", "ENTERED", "EXITING")
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• ENTERING, ENTERED, EXITING, EXITED, HIDDEN
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>Usage Notes</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• Use with openModal() for proper backdrop and positioning
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• Has built-in animation handling based on transitionState
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ListboxItem, ManaListbox } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SAMPLE_ITEMS: ListboxItem[] = [
|
||||
{ id: "1", label: "Apple" },
|
||||
{ id: "2", label: "Banana" },
|
||||
{ id: "3", label: "Cherry" },
|
||||
{ id: "4", label: "Date" },
|
||||
{ id: "5", label: "Elderberry" },
|
||||
];
|
||||
|
||||
const ITEMS_WITH_DISABLED: ListboxItem[] = [
|
||||
{ id: "1", label: "Available option" },
|
||||
{ id: "2", label: "Disabled option", disabled: true },
|
||||
{ id: "3", label: "Another available" },
|
||||
{ id: "4", label: "Also disabled", disabled: true },
|
||||
{ id: "5", label: "Last option" },
|
||||
];
|
||||
|
||||
const MANY_ITEMS: ListboxItem[] = Array.from({ length: 10 }, (_, i) => ({
|
||||
id: String(i + 1),
|
||||
label: `Item ${i + 1}`,
|
||||
}));
|
||||
|
||||
export default function ListboxTab() {
|
||||
const [singleSelected, setSingleSelected] = useState<ListboxItem[]>([SAMPLE_ITEMS[0]]);
|
||||
const [multiSelected, setMultiSelected] = useState<ListboxItem[]>([SAMPLE_ITEMS[0], SAMPLE_ITEMS[2]]);
|
||||
const [disabledSelected, setDisabledSelected] = useState<ListboxItem[]>([]);
|
||||
const [manySelected, setManySelected] = useState<ListboxItem[]>([]);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Single Selection">
|
||||
<ManaListbox
|
||||
items={SAMPLE_ITEMS}
|
||||
selectedItems={singleSelected}
|
||||
onSelectionChange={setSingleSelected}
|
||||
selectionMode="single"
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Multiple Selection">
|
||||
<ManaListbox
|
||||
items={SAMPLE_ITEMS}
|
||||
selectedItems={multiSelected}
|
||||
onSelectionChange={setMultiSelected}
|
||||
selectionMode="multiple"
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Disabled Items">
|
||||
<ManaListbox
|
||||
items={ITEMS_WITH_DISABLED}
|
||||
selectedItems={disabledSelected}
|
||||
onSelectionChange={setDisabledSelected}
|
||||
selectionMode="multiple"
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Max Visible Items (5)">
|
||||
<ManaListbox
|
||||
items={MANY_ITEMS}
|
||||
selectedItems={manySelected}
|
||||
onSelectionChange={setManySelected}
|
||||
selectionMode="multiple"
|
||||
maxVisibleItems={5}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Disabled">
|
||||
<ManaListbox
|
||||
items={SAMPLE_ITEMS}
|
||||
selectedItems={[SAMPLE_ITEMS[1]]}
|
||||
disabled
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Loading">
|
||||
<ManaListbox
|
||||
items={[]}
|
||||
loading
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Empty State">
|
||||
<ManaListbox
|
||||
items={[]}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>Listbox</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• items: ListboxItem[] - Array of items
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• selectedItems?: ListboxItem[] - Currently selected items
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onSelectionChange?: (items: ListboxItem[]) => void - Selection callback
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• selectionMode?: "single" | "multiple" - Selection mode
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• maxVisibleItems?: number - Max visible before scroll
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable entire listbox
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• loading?: boolean - Show loading state
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• typeahead?: boolean - Enable keyboard search
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• shouldFocusWrap?: boolean - Wrap focus at ends
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>ListboxItem</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• id: string - Unique item ID
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• label: string - Display label
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable individual item
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
161
src/equicordplugins/components.dev/components/tabs/ModalTab.tsx
Normal file
161
src/equicordplugins/components.dev/components/tabs/ModalTab.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import {
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalRoot,
|
||||
ModalSize,
|
||||
openModal
|
||||
} from "@utils/modal";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
|
||||
import { ManaButton } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const cl = classNameFactory("vc-compfinder-modal-");
|
||||
|
||||
const ModalCloseButton = findComponentByCodeLazy("CLOSE_BUTTON_LABEL") as React.ComponentType<{
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
withCircleBackground?: boolean;
|
||||
hideOnFullscreen?: boolean;
|
||||
}>;
|
||||
|
||||
const SIZES = [
|
||||
{ size: ModalSize.SMALL, label: "Small" },
|
||||
{ size: ModalSize.MEDIUM, label: "Medium" },
|
||||
{ size: ModalSize.LARGE, label: "Large" },
|
||||
{ size: ModalSize.DYNAMIC, label: "Dynamic" },
|
||||
] as const;
|
||||
|
||||
function openDemoModal(size: ModalSize, label: string, showCloseButton = true, showFooter = true) {
|
||||
openModal(props => (
|
||||
<ModalRoot {...props} size={size}>
|
||||
<ModalHeader separator={false} className={cl("header")}>
|
||||
<div className={cl("header-content")}>
|
||||
<Heading tag="h2">
|
||||
{label} Modal
|
||||
</Heading>
|
||||
<Paragraph color="text-muted">
|
||||
This is a {label.toLowerCase()} modal using Discord's native modal system.
|
||||
</Paragraph>
|
||||
</div>
|
||||
{showCloseButton && (
|
||||
<ModalCloseButton
|
||||
onClick={props.onClose}
|
||||
className={cl("close")}
|
||||
/>
|
||||
)}
|
||||
</ModalHeader>
|
||||
<ModalContent className={cl("content")}>
|
||||
<Paragraph color="text-muted">
|
||||
Size: {size}
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 8 }}>
|
||||
The modal header, content, and footer use Discord's native components
|
||||
from the webpack modules.
|
||||
</Paragraph>
|
||||
</ModalContent>
|
||||
{showFooter && (
|
||||
<ModalFooter className={cl("footer")}>
|
||||
<ManaButton variant="primary" text="Confirm" onClick={props.onClose} />
|
||||
<ManaButton variant="secondary" text="Cancel" onClick={props.onClose} />
|
||||
</ModalFooter>
|
||||
)}
|
||||
</ModalRoot>
|
||||
));
|
||||
}
|
||||
|
||||
export default function ModalTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Modal Sizes">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Uses ModalRoot, ModalHeader, ModalContent, ModalFooter from @utils/modal
|
||||
and native ModalCloseButton via findComponentByCodeLazy("CLOSE_BUTTON_LABEL")
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{SIZES.map(({ size, label }) => (
|
||||
<ManaButton
|
||||
key={size}
|
||||
variant="secondary"
|
||||
text={label}
|
||||
onClick={() => openDemoModal(size, label)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Without Close Button">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text="Open Modal"
|
||||
onClick={() => openDemoModal(ModalSize.SMALL, "No Close Button", false, true)}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Without Footer">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text="Open Modal"
|
||||
onClick={() => openDemoModal(ModalSize.SMALL, "No Footer", true, false)}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Minimal (No Close Button or Footer)">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text="Open Modal"
|
||||
onClick={() => openDemoModal(ModalSize.DYNAMIC, "Minimal", false, false)}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>ModalRoot</strong> - from @utils/modal
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• size: ModalSize - SMALL, MEDIUM, LARGE, or DYNAMIC
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• ...modalProps - Props from openModal callback
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>ModalHeader</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• separator?: boolean - Show separator line (default: true)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>ModalCloseButton</strong> - findComponentByCodeLazy("CLOSE_BUTTON_LABEL")
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onClick: () => void - Close handler
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• withCircleBackground?: boolean - Circle background style
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>openModal</strong> - from @utils/modal
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• (render: (props) => ReactNode) => string - Returns modal key
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
132
src/equicordplugins/components.dev/components/tabs/NoticeTab.tsx
Normal file
132
src/equicordplugins/components.dev/components/tabs/NoticeTab.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Button } from "@components/Button";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { Notice, NoticeType } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const NOTICE_TYPES: NoticeType[] = ["info", "warn", "danger", "positive", "preview"];
|
||||
|
||||
export default function NoticeTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Message Types">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Notice component for displaying informational messages with different severity levels.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{NOTICE_TYPES.map(type => (
|
||||
<div key={type} style={{ textAlign: "center" }}>
|
||||
<Notice messageType={type}>
|
||||
This is a {type} notice message.
|
||||
</Notice>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
{type}
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Action">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Notices can include action buttons for user interaction.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<Notice
|
||||
messageType="info"
|
||||
action={<Button size="small" variant="secondary">Action</Button>}
|
||||
>
|
||||
Notice with an action button.
|
||||
</Notice>
|
||||
<Notice
|
||||
messageType="warn"
|
||||
action={<Button size="small" variant="primary">Fix Now</Button>}
|
||||
>
|
||||
Warning that requires attention.
|
||||
</Notice>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Hidden State">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
The hidden prop controls visibility without removing from DOM.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Notice messageType="info" hidden={false}>
|
||||
This notice is visible.
|
||||
</Notice>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
hidden=false
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Notice messageType="info" hidden={true}>
|
||||
This notice is hidden.
|
||||
</Notice>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
hidden=true
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Text Variants">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Custom text styling via the textVariant prop.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Notice messageType="info">
|
||||
Default text variant.
|
||||
</Notice>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
text-sm/medium (default)
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Notice messageType="info" textVariant="text-md/normal">
|
||||
Larger text variant.
|
||||
</Notice>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
text-md/normal
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• children: ReactNode - Notice content
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• messageType: "info" | "warn" | "danger" | "positive" | "preview"
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• action?: ReactNode - Action button or element
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• hidden?: boolean - Hide the notice
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• textColor?: string - Custom text color
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• textVariant?: string - Text size/weight variant
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• icon?: ComponentType - Custom icon component
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• className?: string - Additional CSS class
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { Paginator2 as Paginator } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function PaginatorTab() {
|
||||
const [page1, setPage1] = useState(1);
|
||||
const [page2, setPage2] = useState(1);
|
||||
const [page3, setPage3] = useState(1);
|
||||
const [page4, setPage4] = useState(1);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Pagination component for navigating through pages of content.
|
||||
</Paragraph>
|
||||
<Paginator
|
||||
currentPage={page1}
|
||||
totalCount={100}
|
||||
pageSize={10}
|
||||
onPageChange={setPage1}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Max Visible Pages">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Control how many page numbers are visible at once.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Paginator
|
||||
currentPage={page2}
|
||||
totalCount={100}
|
||||
pageSize={10}
|
||||
maxVisiblePages={3}
|
||||
onPageChange={setPage2}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
3 visible
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Paginator
|
||||
currentPage={page3}
|
||||
totalCount={100}
|
||||
pageSize={10}
|
||||
maxVisiblePages={7}
|
||||
onPageChange={setPage3}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
7 visible
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Options">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Additional configuration options for the paginator.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Paginator
|
||||
currentPage={page4}
|
||||
totalCount={100}
|
||||
pageSize={10}
|
||||
hideMaxPage
|
||||
onPageChange={setPage4}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
hideMaxPage
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Paginator
|
||||
currentPage={page4}
|
||||
totalCount={50}
|
||||
pageSize={10}
|
||||
disablePaginationGap
|
||||
onPageChange={setPage4}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
disablePaginationGap
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• currentPage: number - Current active page
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• totalCount: number - Total number of items
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• pageSize: number - Items per page
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onPageChange?: (page: number) => void - Page change callback
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• maxVisiblePages?: number - Max page numbers shown
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• hideMaxPage?: boolean - Hide last page number
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disablePaginationGap?: boolean - Remove ellipsis gaps
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• className?: string - Additional CSS class
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useRef, useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton, ManaPopover, PopoverAction } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
function PopoverDemo({ title, body, size, actions, position, label }: { title: string; body: string; size?: "sm" | "md" | "lg"; actions?: PopoverAction[]; position?: "top" | "bottom" | "left" | "right"; label: string; }) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={wrapperRef} style={{ display: "inline-block" }}>
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={label}
|
||||
onClick={() => setShow(!show)}
|
||||
/>
|
||||
</div>
|
||||
<ManaPopover
|
||||
targetElementRef={wrapperRef}
|
||||
shouldShow={show}
|
||||
onRequestClose={() => setShow(false)}
|
||||
title={title}
|
||||
body={body}
|
||||
size={size}
|
||||
actions={actions}
|
||||
position={position}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PopoverTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic Popover">
|
||||
<div className="vc-compfinder-grid">
|
||||
<PopoverDemo
|
||||
label="Click me"
|
||||
title="Popover Title"
|
||||
body="This is the popover body text with some helpful information."
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Sizes">
|
||||
<div className="vc-compfinder-grid">
|
||||
<PopoverDemo
|
||||
label="sm"
|
||||
title="Small Popover"
|
||||
body="This is a small popover."
|
||||
size="sm"
|
||||
/>
|
||||
<PopoverDemo
|
||||
label="md"
|
||||
title="Medium Popover"
|
||||
body="This is a medium popover (default)."
|
||||
size="md"
|
||||
/>
|
||||
<PopoverDemo
|
||||
label="lg"
|
||||
title="Large Popover"
|
||||
body="This is a large popover with more space."
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Positions">
|
||||
<div className="vc-compfinder-grid">
|
||||
<PopoverDemo
|
||||
label="top"
|
||||
title="Top Position"
|
||||
body="Popover appears above."
|
||||
position="top"
|
||||
/>
|
||||
<PopoverDemo
|
||||
label="bottom"
|
||||
title="Bottom Position"
|
||||
body="Popover appears below."
|
||||
position="bottom"
|
||||
/>
|
||||
<PopoverDemo
|
||||
label="left"
|
||||
title="Left Position"
|
||||
body="Popover appears to the left."
|
||||
position="left"
|
||||
/>
|
||||
<PopoverDemo
|
||||
label="right"
|
||||
title="Right Position"
|
||||
body="Popover appears to the right."
|
||||
position="right"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Actions">
|
||||
<div className="vc-compfinder-grid">
|
||||
<PopoverDemo
|
||||
label="With Actions"
|
||||
title="Popover with Actions"
|
||||
body="This popover has action buttons."
|
||||
actions={[
|
||||
{ text: "Primary", variant: "primary" },
|
||||
{ text: "Secondary", variant: "secondary" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• targetElementRef: React.RefObject - Ref to trigger element
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• shouldShow: boolean - Controls visibility
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onRequestClose?: (reason: string) => void - Close callback
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• title?: string - Popover title
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• body?: string - Popover body text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• size?: "sm" | "md" | "lg" - Popover size
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• position?: "top" | "bottom" | "left" | "right" - Position
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• actions?: PopoverAction[] - Action buttons
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• textLink?: {"{"} text: string, onClick?: () => void {"}"} - Link
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• graphic?: {"{"} src: string, aspectRatio?: string {"}"} - Image
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { ProgressBar } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function ProgressBarTab() {
|
||||
const [animatedProgress, setAnimatedProgress] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setAnimatedProgress(prev => (prev >= 100 ? 0 : prev + 5));
|
||||
}, 200);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Variants">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Blue (default) and Orange variants.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 4 }}>Blue:</Paragraph>
|
||||
<ProgressBar progress={60} variant="blue" />
|
||||
</div>
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 4 }}>Orange:</Paragraph>
|
||||
<ProgressBar progress={60} variant="orange" />
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Progress Values">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 4 }}>0%:</Paragraph>
|
||||
<ProgressBar progress={0} variant="blue" />
|
||||
</div>
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 4 }}>25%:</Paragraph>
|
||||
<ProgressBar progress={25} variant="blue" />
|
||||
</div>
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 4 }}>50%:</Paragraph>
|
||||
<ProgressBar progress={50} variant="blue" />
|
||||
</div>
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 4 }}>75%:</Paragraph>
|
||||
<ProgressBar progress={75} variant="blue" />
|
||||
</div>
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 4 }}>100%:</Paragraph>
|
||||
<ProgressBar progress={100} variant="blue" />
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Animated">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Progress: {animatedProgress}%
|
||||
</Paragraph>
|
||||
<ProgressBar progress={animatedProgress} variant="blue" />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Custom Range">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Using minimum=0 and maximum=200, progress=150 (75% filled).
|
||||
</Paragraph>
|
||||
<ProgressBar progress={150} minimum={0} maximum={200} variant="orange" />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Custom Override">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Using variant="unset" with custom gradient colors.
|
||||
</Paragraph>
|
||||
<ProgressBar
|
||||
progress={70}
|
||||
variant="unset"
|
||||
override={{
|
||||
default: {
|
||||
background: "rgba(88, 101, 242, 0.3)",
|
||||
gradientStart: "#5865F2",
|
||||
gradientEnd: "#EB459E"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• progress: number - Current progress value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• minimum?: number - Minimum value (default: 0)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• maximum?: number - Maximum value (default: 100)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• variant?: "blue" | "orange" | "unset" - Color variant
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• override?: object - Custom colors when variant="unset"
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• labelledBy?: string - ID of labelling element for a11y
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaBaseRadioGroup, RadioOption, StandaloneRadioIndicator } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const BASIC_OPTIONS: RadioOption[] = [
|
||||
{ value: "option1", name: "Option 1" },
|
||||
{ value: "option2", name: "Option 2" },
|
||||
{ value: "option3", name: "Option 3" },
|
||||
];
|
||||
|
||||
const OPTIONS_WITH_DESCRIPTIONS: RadioOption[] = [
|
||||
{ value: "low", name: "Low Quality", desc: "Faster loading, uses less data" },
|
||||
{ value: "medium", name: "Medium Quality", desc: "Balanced quality and performance" },
|
||||
{ value: "high", name: "High Quality", desc: "Best quality, uses more data" },
|
||||
];
|
||||
|
||||
const OPTIONS_WITH_ICONS: RadioOption[] = [
|
||||
{ value: "item1", name: "Item with icon", desc: "This option has an icon", leadingIcon: EquicordIcon },
|
||||
{ value: "item2", name: "Another with icon", leadingIcon: EquicordIcon },
|
||||
{ value: "item3", name: "Third with icon", leadingIcon: EquicordIcon },
|
||||
];
|
||||
|
||||
const OPTIONS_MIXED: RadioOption[] = [
|
||||
{ value: "enabled", name: "Enabled option" },
|
||||
{ value: "disabled", name: "Disabled option", disabled: true },
|
||||
{ value: "another", name: "Another enabled" },
|
||||
];
|
||||
|
||||
const NUMBERED_OPTIONS: RadioOption[] = [
|
||||
{ value: 1, name: "First choice" },
|
||||
{ value: 2, name: "Second choice" },
|
||||
{ value: 3, name: "Third choice" },
|
||||
];
|
||||
|
||||
export default function RadioGroupTab() {
|
||||
const [basicValue, setBasicValue] = useState<string | number>("option1");
|
||||
const [descValue, setDescValue] = useState<string | number>("medium");
|
||||
const [iconValue, setIconValue] = useState<string | number>("item1");
|
||||
const [mixedValue, setMixedValue] = useState<string | number>("enabled");
|
||||
const [numberedValue, setNumberedValue] = useState<number>(1);
|
||||
const [unselectedValue, setUnselectedValue] = useState<string | number | undefined>(undefined);
|
||||
const [standaloneSelected, setStandaloneSelected] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Simple radio group with text-only options.
|
||||
</Paragraph>
|
||||
<ManaBaseRadioGroup
|
||||
options={BASIC_OPTIONS}
|
||||
value={basicValue}
|
||||
onChange={setBasicValue}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Descriptions">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Radio options with descriptive text using the desc prop.
|
||||
</Paragraph>
|
||||
<ManaBaseRadioGroup
|
||||
options={OPTIONS_WITH_DESCRIPTIONS}
|
||||
value={descValue}
|
||||
onChange={setDescValue}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Leading Icons">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Radio options with icons using leadingIcon prop.
|
||||
</Paragraph>
|
||||
<ManaBaseRadioGroup
|
||||
options={OPTIONS_WITH_ICONS}
|
||||
value={iconValue}
|
||||
onChange={setIconValue}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Disabled Option">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Individual options can be disabled with disabled: true.
|
||||
</Paragraph>
|
||||
<ManaBaseRadioGroup
|
||||
options={OPTIONS_MIXED}
|
||||
value={mixedValue}
|
||||
onChange={setMixedValue}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Numeric Values">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Radio options can use numeric values.
|
||||
</Paragraph>
|
||||
<ManaBaseRadioGroup
|
||||
options={NUMBERED_OPTIONS}
|
||||
value={numberedValue}
|
||||
onChange={v => setNumberedValue(v as number)}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="No Initial Selection">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Radio group with no initial selection (value undefined).
|
||||
</Paragraph>
|
||||
<ManaBaseRadioGroup
|
||||
options={BASIC_OPTIONS}
|
||||
value={unselectedValue}
|
||||
onChange={setUnselectedValue}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Entire Group Disabled">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
The entire group can be disabled with disabled prop.
|
||||
</Paragraph>
|
||||
<ManaBaseRadioGroup
|
||||
options={BASIC_OPTIONS}
|
||||
value="option2"
|
||||
disabled
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Standalone Radio Indicator">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
A standalone animated radio indicator component. Click to toggle.
|
||||
</Paragraph>
|
||||
<div
|
||||
onClick={() => setStandaloneSelected(!standaloneSelected)}
|
||||
style={{ cursor: "pointer", display: "inline-block" }}
|
||||
>
|
||||
<StandaloneRadioIndicator isSelected={standaloneSelected} />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>BaseRadioGroup</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• options: RadioOption[] - Array of radio options
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• value?: string | number - Currently selected value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (value: string | number) => void - Called when selection changes
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable entire group
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• aria-labelledby?: string - Accessibility label reference
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>RadioOption</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• value: string | number - Option value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• name: string - Option label text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• desc?: string - Optional description text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable individual option
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• leadingIcon?: React.ComponentType - Icon before radio
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>StandaloneRadioIndicator</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• isSelected: boolean - Whether the indicator is selected
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable the indicator
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { ManaButton, ManaRichTooltip } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function RichTooltipTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Rich Tooltip - Basic">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Rich tooltip with title and body text.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaRichTooltip
|
||||
title="Tooltip Title"
|
||||
body="This is the body text of the rich tooltip with more detailed information."
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Hover me" />
|
||||
</ManaRichTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Rich Tooltip - Body Only">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Rich tooltip without title.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaRichTooltip
|
||||
body="This tooltip only has body text, no title."
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Body Only" />
|
||||
</ManaRichTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Positions">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaRichTooltip
|
||||
title="Top"
|
||||
body="Positioned at top"
|
||||
position="top"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Top" />
|
||||
</ManaRichTooltip>
|
||||
<ManaRichTooltip
|
||||
title="Bottom"
|
||||
body="Positioned at bottom"
|
||||
position="bottom"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Bottom" />
|
||||
</ManaRichTooltip>
|
||||
<ManaRichTooltip
|
||||
title="Left"
|
||||
body="Positioned at left"
|
||||
position="left"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Left" />
|
||||
</ManaRichTooltip>
|
||||
<ManaRichTooltip
|
||||
title="Right"
|
||||
body="Positioned at right"
|
||||
position="right"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Right" />
|
||||
</ManaRichTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Alignment">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaRichTooltip
|
||||
title="Start Aligned"
|
||||
body="Tooltip aligned to start"
|
||||
align="start"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Start" />
|
||||
</ManaRichTooltip>
|
||||
<ManaRichTooltip
|
||||
title="Center Aligned"
|
||||
body="Tooltip aligned to center"
|
||||
align="center"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Center" />
|
||||
</ManaRichTooltip>
|
||||
<ManaRichTooltip
|
||||
title="End Aligned"
|
||||
body="Tooltip aligned to end"
|
||||
align="end"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="End" />
|
||||
</ManaRichTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Custom Spacing">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaRichTooltip
|
||||
title="More Spacing"
|
||||
body="This tooltip has increased spacing from the target element."
|
||||
spacing={16}
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="16px Spacing" />
|
||||
</ManaRichTooltip>
|
||||
<ManaRichTooltip
|
||||
title="Less Spacing"
|
||||
body="This tooltip has minimal spacing."
|
||||
spacing={4}
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="4px Spacing" />
|
||||
</ManaRichTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Container Element Types">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaRichTooltip
|
||||
title="Span Container"
|
||||
body="Uses span as container element (default)"
|
||||
element="span"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Span" />
|
||||
</ManaRichTooltip>
|
||||
<ManaRichTooltip
|
||||
title="Div Container"
|
||||
body="Uses div as container element"
|
||||
element="div"
|
||||
asContainer
|
||||
>
|
||||
<ManaButton variant="secondary" text="Div" />
|
||||
</ManaRichTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• title?: string - Tooltip title (bold)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• body?: string - Tooltip body text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• children: ReactNode - Trigger element
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• position?: "top" | "bottom" | "left" | "right" - Position
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• align?: "start" | "center" | "end" - Alignment
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• spacing?: number - Distance from trigger (px)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• asContainer?: boolean - Wrap children in container
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• element?: "span" | "div" - Container element type
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { SearchBar } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function SearchBarTab() {
|
||||
const [query1, setQuery1] = useState("");
|
||||
const [query2, setQuery2] = useState("");
|
||||
const [query3, setQuery3] = useState("prefilled");
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Default search bar with clear button.
|
||||
</Paragraph>
|
||||
<SearchBar
|
||||
query={query1}
|
||||
onChange={setQuery1}
|
||||
onClear={() => setQuery1("")}
|
||||
placeholder="Search..."
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Sizes">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Available sizes: sm, md (default).
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<SearchBar
|
||||
query={query2}
|
||||
onChange={setQuery2}
|
||||
onClear={() => setQuery2("")}
|
||||
placeholder="Small size"
|
||||
size="sm"
|
||||
/>
|
||||
<SearchBar
|
||||
query={query2}
|
||||
onChange={setQuery2}
|
||||
onClear={() => setQuery2("")}
|
||||
placeholder="Medium size"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="States">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Disabled and prefilled states.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<SearchBar
|
||||
query=""
|
||||
onChange={() => { }}
|
||||
placeholder="Disabled"
|
||||
disabled
|
||||
/>
|
||||
<SearchBar
|
||||
query={query3}
|
||||
onChange={setQuery3}
|
||||
onClear={() => setQuery3("")}
|
||||
placeholder="Prefilled"
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
query, onChange, onClear?, placeholder?, size?, autoFocus?, disabled?, onKeyDown?, onBlur?, onFocus?, autoComplete?, inputProps?, aria-label?
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
123
src/equicordplugins/components.dev/components/tabs/SelectTab.tsx
Normal file
123
src/equicordplugins/components.dev/components/tabs/SelectTab.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaSelect, ManaSelectOption } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SAMPLE_OPTIONS: ManaSelectOption[] = [
|
||||
{ id: "1", value: "option1", label: "Option 1" },
|
||||
{ id: "2", value: "option2", label: "Option 2" },
|
||||
{ id: "3", value: "option3", label: "Option 3" },
|
||||
{ id: "4", value: "option4", label: "Option 4" },
|
||||
];
|
||||
|
||||
export default function SelectTab() {
|
||||
const [singleValue, setSingleValue] = useState<string>("option1");
|
||||
const [multiValue, setMultiValue] = useState<string[]>(["option1", "option2"]);
|
||||
const [clearableValue, setClearableValue] = useState<string>("option1");
|
||||
const [placeholderValue, setPlaceholderValue] = useState<string>("");
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Single Selection">
|
||||
<ManaSelect
|
||||
options={SAMPLE_OPTIONS}
|
||||
value={singleValue}
|
||||
onSelectionChange={v => setSingleValue(v as string)}
|
||||
selectionMode="single"
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Multiple Selection">
|
||||
<ManaSelect
|
||||
options={SAMPLE_OPTIONS}
|
||||
value={multiValue}
|
||||
onSelectionChange={v => setMultiValue(v as string[])}
|
||||
selectionMode="multiple"
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Placeholder">
|
||||
<ManaSelect
|
||||
options={SAMPLE_OPTIONS}
|
||||
value={placeholderValue}
|
||||
onSelectionChange={v => setPlaceholderValue(v as string)}
|
||||
placeholder="Select an option..."
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Clearable">
|
||||
<ManaSelect
|
||||
options={SAMPLE_OPTIONS}
|
||||
value={clearableValue}
|
||||
onSelectionChange={v => setClearableValue(v as string)}
|
||||
clearable
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Full Width">
|
||||
<ManaSelect
|
||||
options={SAMPLE_OPTIONS}
|
||||
value="option1"
|
||||
fullWidth
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Disabled">
|
||||
<ManaSelect
|
||||
options={SAMPLE_OPTIONS}
|
||||
value="option2"
|
||||
disabled
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>ManaSelect</strong> - Note: Uses onSelectionChange, NOT onChange.
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• options: ManaSelectOption[] - Array of options
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• value?: string | string[] | null - Selected value(s)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onSelectionChange?: (value) => void - Called when selection changes
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• selectionMode?: "single" | "multiple" - Selection mode
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• placeholder?: string - Placeholder text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• clearable?: boolean - Allow clearing selection
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• fullWidth?: boolean - Expand to full width
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable select
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>ManaSelectOption</strong>
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• id: string - Unique option ID
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• value: string - Option value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• label: string - Display label
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
110
src/equicordplugins/components.dev/components/tabs/SliderTab.tsx
Normal file
110
src/equicordplugins/components.dev/components/tabs/SliderTab.tsx
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { Slider } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function SliderTab() {
|
||||
const [basicValue, setBasicValue] = useState(50);
|
||||
const [markerValue, setMarkerValue] = useState(50);
|
||||
const [miniValue, setMiniValue] = useState(30);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic Slider">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Value: {basicValue}
|
||||
</Paragraph>
|
||||
<Slider
|
||||
initialValue={basicValue}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
onValueChange={setBasicValue}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Markers">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Value: {markerValue}
|
||||
</Paragraph>
|
||||
<Slider
|
||||
initialValue={markerValue}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
markers={[0, 25, 50, 75, 100]}
|
||||
onValueChange={setMarkerValue}
|
||||
onMarkerRender={(v: number) => `${v}%`}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Stick to Markers">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Value snaps to markers.
|
||||
</Paragraph>
|
||||
<Slider
|
||||
initialValue={50}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
markers={[0, 25, 50, 75, 100]}
|
||||
stickToMarkers
|
||||
onMarkerRender={(v: number) => `${v}%`}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Mini Slider">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Value: {miniValue}
|
||||
</Paragraph>
|
||||
<div style={{ width: 200 }}>
|
||||
<Slider
|
||||
initialValue={miniValue}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
mini
|
||||
onValueChange={setMiniValue}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Disabled">
|
||||
<Slider
|
||||
initialValue={60}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
disabled
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Custom Range">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Range: -50 to 50
|
||||
</Paragraph>
|
||||
<Slider
|
||||
initialValue={0}
|
||||
minValue={-50}
|
||||
maxValue={50}
|
||||
markers={[-50, -25, 0, 25, 50]}
|
||||
onMarkerRender={(v: number) => v.toString()}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• initialValue / value - Current value</Paragraph>
|
||||
<Paragraph color="text-muted">• minValue / maxValue - Range bounds</Paragraph>
|
||||
<Paragraph color="text-muted">• onValueChange - Change callback</Paragraph>
|
||||
<Paragraph color="text-muted">• markers - Array of marker values</Paragraph>
|
||||
<Paragraph color="text-muted">• stickToMarkers - Snap to markers</Paragraph>
|
||||
<Paragraph color="text-muted">• onMarkerRender - Custom marker labels</Paragraph>
|
||||
<Paragraph color="text-muted">• mini - Compact style</Paragraph>
|
||||
<Paragraph color="text-muted">• hideBubble - Hide value tooltip</Paragraph>
|
||||
<Paragraph color="text-muted">• disabled - Disable interaction</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton, Spinner } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SPINNER_TYPES = [
|
||||
"wanderingCubes",
|
||||
"chasingDots",
|
||||
"pulsingEllipsis",
|
||||
"spinningCircle",
|
||||
"spinningCircleSimple",
|
||||
"lowMotion"
|
||||
] as const;
|
||||
|
||||
export default function SpinnerTab() {
|
||||
const [animated, setAnimated] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Spinner Types">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
All available spinner variants.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{SPINNER_TYPES.map(type => (
|
||||
<div key={type} style={{ textAlign: "center", padding: 16 }}>
|
||||
<Spinner type={type} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 8 }}>
|
||||
{type}
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Animation Toggle">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Spinners can be paused with animated=false.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={animated ? "Pause Animation" : "Resume Animation"}
|
||||
onClick={() => setAnimated(!animated)}
|
||||
/>
|
||||
</div>
|
||||
<div className="vc-compfinder-grid" style={{ marginTop: 16 }}>
|
||||
<div style={{ textAlign: "center", padding: 16 }}>
|
||||
<Spinner type="spinningCircle" animated={animated} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 8 }}>
|
||||
animated={animated.toString()}
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Reduced Motion">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
lowMotion type is used when user prefers reduced motion.
|
||||
The component automatically switches to this when reducedMotion is enabled.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ textAlign: "center", padding: 16 }}>
|
||||
<Spinner type="lowMotion" />
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• type?: "wanderingCubes" | "chasingDots" | "pulsingEllipsis" | "spinningCircle" | "spinningCircleSimple" | "lowMotion"
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• animated?: boolean - Enable/disable animation (default: true)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• className?: string - Container class
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• itemClassName?: string - Inner item class
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• aria-label?: string - Accessibility label
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
131
src/equicordplugins/components.dev/components/tabs/SwitchTab.tsx
Normal file
131
src/equicordplugins/components.dev/components/tabs/SwitchTab.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaSwitch } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
interface SwitchWithLabelProps {
|
||||
checked: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const SwitchWithLabel = findComponentByCodeLazy('"data-toggleable-component":"switch"') as React.ComponentType<SwitchWithLabelProps>;
|
||||
|
||||
export default function SwitchTab() {
|
||||
const [basicOn, setBasicOn] = useState(true);
|
||||
const [basicOff, setBasicOff] = useState(false);
|
||||
|
||||
const [labelOn, setLabelOn] = useState(true);
|
||||
const [labelOff, setLabelOff] = useState(false);
|
||||
|
||||
const [withDesc, setWithDesc] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic Switch (ManaSwitch)">
|
||||
<div className="vc-compfinder-grid">
|
||||
<div className="vc-compfinder-switch-item">
|
||||
<span>On:</span>
|
||||
<ManaSwitch checked={basicOn} onChange={setBasicOn} />
|
||||
</div>
|
||||
<div className="vc-compfinder-switch-item">
|
||||
<span>Off:</span>
|
||||
<ManaSwitch checked={basicOff} onChange={setBasicOff} />
|
||||
</div>
|
||||
<div className="vc-compfinder-switch-item">
|
||||
<span>Disabled On:</span>
|
||||
<ManaSwitch checked={true} disabled />
|
||||
</div>
|
||||
<div className="vc-compfinder-switch-item">
|
||||
<span>Disabled Off:</span>
|
||||
<ManaSwitch checked={false} disabled />
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Switch with Label">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<SwitchWithLabel
|
||||
label="Enabled option"
|
||||
checked={labelOn}
|
||||
onChange={setLabelOn}
|
||||
/>
|
||||
<SwitchWithLabel
|
||||
label="Disabled option"
|
||||
checked={labelOff}
|
||||
onChange={setLabelOff}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Switch with Description">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<SwitchWithLabel
|
||||
label="Feature toggle"
|
||||
description="Enable this feature to unlock additional functionality."
|
||||
checked={withDesc}
|
||||
onChange={setWithDesc}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Disabled States">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<SwitchWithLabel
|
||||
label="Disabled (checked)"
|
||||
checked={true}
|
||||
disabled
|
||||
/>
|
||||
<SwitchWithLabel
|
||||
label="Disabled (unchecked)"
|
||||
checked={false}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>ManaSwitch</strong> - Basic toggle switch without label.
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• checked: boolean - Current switch state
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (checked: boolean) => void - Called when toggled
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable interaction
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>SwitchWithLabel</strong> - Switch with label and description.
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• label?: string - Label text displayed next to switch
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• description?: string - Description text below label
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• checked: boolean - Current switch state
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (checked: boolean) => void - Called when toggled
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable interaction
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
191
src/equicordplugins/components.dev/components/tabs/TabBarTab.tsx
Normal file
191
src/equicordplugins/components.dev/components/tabs/TabBarTab.tsx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { TabBar } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function TabBarTab() {
|
||||
const [sideTab, setSideTab] = useState("tab1");
|
||||
const [topTab, setTopTab] = useState("tab1");
|
||||
const [topPillTab, setTopPillTab] = useState("tab1");
|
||||
const [brandTab, setBrandTab] = useState("tab1");
|
||||
const [colorTab, setColorTab] = useState("tab1");
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Side TabBar (Default)">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Default side-style tab bar with grey look. Selected: {sideTab}
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="side"
|
||||
look="grey"
|
||||
selectedItem={sideTab}
|
||||
onItemSelect={setSideTab}
|
||||
>
|
||||
<TabBar.Item id="tab1">First Tab</TabBar.Item>
|
||||
<TabBar.Item id="tab2">Second Tab</TabBar.Item>
|
||||
<TabBar.Item id="tab3">Third Tab</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Top TabBar">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Top-style tab bar with underline indicator. Selected: {topTab}
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="top"
|
||||
look="grey"
|
||||
selectedItem={topTab}
|
||||
onItemSelect={setTopTab}
|
||||
>
|
||||
<TabBar.Item id="tab1">Overview</TabBar.Item>
|
||||
<TabBar.Item id="tab2">Settings</TabBar.Item>
|
||||
<TabBar.Item id="tab3">Members</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Top Pill TabBar">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Pill-style tabs for top navigation. Selected: {topPillTab}
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="top-pill"
|
||||
look="grey"
|
||||
selectedItem={topPillTab}
|
||||
onItemSelect={setTopPillTab}
|
||||
>
|
||||
<TabBar.Item id="tab1">All</TabBar.Item>
|
||||
<TabBar.Item id="tab2">Online</TabBar.Item>
|
||||
<TabBar.Item id="tab3">Pending</TabBar.Item>
|
||||
<TabBar.Item id="tab4">Blocked</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Brand Look">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Brand-colored tab bar style. Selected: {brandTab}
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="top"
|
||||
look="brand"
|
||||
selectedItem={brandTab}
|
||||
onItemSelect={setBrandTab}
|
||||
>
|
||||
<TabBar.Item id="tab1">Home</TabBar.Item>
|
||||
<TabBar.Item id="tab2">Explore</TabBar.Item>
|
||||
<TabBar.Item id="tab3">Library</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Header and Separator">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Tab bar with header section and separator.
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="side"
|
||||
look="grey"
|
||||
selectedItem={sideTab}
|
||||
onItemSelect={setSideTab}
|
||||
>
|
||||
<TabBar.Header>User Settings</TabBar.Header>
|
||||
<TabBar.Item id="tab1">My Account</TabBar.Item>
|
||||
<TabBar.Item id="tab2">Privacy</TabBar.Item>
|
||||
<TabBar.Separator />
|
||||
<TabBar.Header>App Settings</TabBar.Header>
|
||||
<TabBar.Item id="tab3">Appearance</TabBar.Item>
|
||||
<TabBar.Item id="tab4">Notifications</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Disabled Items">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Some tabs can be disabled.
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="top"
|
||||
look="grey"
|
||||
selectedItem={topTab}
|
||||
onItemSelect={setTopTab}
|
||||
>
|
||||
<TabBar.Item id="tab1">Active</TabBar.Item>
|
||||
<TabBar.Item id="tab2" disabled>Disabled</TabBar.Item>
|
||||
<TabBar.Item id="tab3">Another Active</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Custom Colors">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Individual tabs can have custom colors. Selected: {colorTab}
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="top"
|
||||
look="grey"
|
||||
selectedItem={colorTab}
|
||||
onItemSelect={setColorTab}
|
||||
>
|
||||
<TabBar.Item id="tab1" color="#43b581">Green</TabBar.Item>
|
||||
<TabBar.Item id="tab2" color="#faa61a">Yellow</TabBar.Item>
|
||||
<TabBar.Item id="tab3" color="#f04747">Red</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Destructive Variant">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Tab with destructive/danger styling.
|
||||
</Paragraph>
|
||||
<TabBar
|
||||
type="side"
|
||||
look="grey"
|
||||
selectedItem={sideTab}
|
||||
onItemSelect={setSideTab}
|
||||
>
|
||||
<TabBar.Item id="tab1">Settings</TabBar.Item>
|
||||
<TabBar.Item id="tab2">Privacy</TabBar.Item>
|
||||
<TabBar.Item id="logout" variant="destructive">Log Out</TabBar.Item>
|
||||
</TabBar>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>TabBar</strong> - Container component
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• type?: "side" | "top" | "top-pill" - Tab bar style</Paragraph>
|
||||
<Paragraph color="text-muted">• look?: "brand" | "grey" - Color scheme</Paragraph>
|
||||
<Paragraph color="text-muted">• selectedItem?: string - Currently selected tab ID</Paragraph>
|
||||
<Paragraph color="text-muted">• onItemSelect?: (id) ={">"} void - Selection callback</Paragraph>
|
||||
<Paragraph color="text-muted">• orientation?: "horizontal" | "vertical" - Layout direction</Paragraph>
|
||||
<Paragraph color="text-muted">• className?: string - Additional CSS classes</Paragraph>
|
||||
<Paragraph color="text-muted">• style?: CSSProperties - Inline styles</Paragraph>
|
||||
<Paragraph color="text-muted">• aria-label?: string - Accessibility label</Paragraph>
|
||||
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>TabBar.Item</strong> - Individual tab
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• id: string - Unique tab identifier (required)</Paragraph>
|
||||
<Paragraph color="text-muted">• disabled?: boolean - Disable the tab</Paragraph>
|
||||
<Paragraph color="text-muted">• color?: string - Custom tab color</Paragraph>
|
||||
<Paragraph color="text-muted">• variant?: "destructive" - Danger styling</Paragraph>
|
||||
<Paragraph color="text-muted">• disableItemStyles?: boolean - Remove default styles</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick?: MouseEventHandler - Custom click handler</Paragraph>
|
||||
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>TabBar.Header</strong> - Section header
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• children: ReactNode - Header text</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick?: MouseEventHandler - Click handler</Paragraph>
|
||||
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>TabBar.Separator</strong> - Visual divider
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">• style?: CSSProperties - Custom styles</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaTextArea } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function TextAreaTab() {
|
||||
const [basic, setBasic] = useState("Hello world\nThis is a textarea");
|
||||
const [withPlaceholder, setWithPlaceholder] = useState("");
|
||||
const [autosize, setAutosize] = useState("This textarea will grow as you type more content...");
|
||||
const [fixedRows, setFixedRows] = useState("Fixed 5 rows");
|
||||
const [charCount, setCharCount] = useState("Count characters");
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic">
|
||||
<ManaTextArea value={basic} onChange={setBasic} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Placeholder">
|
||||
<ManaTextArea value={withPlaceholder} onChange={setWithPlaceholder} placeholder="Enter your message..." />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Autosize">
|
||||
<ManaTextArea value={autosize} onChange={setAutosize} autosize />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Fixed Rows (5)">
|
||||
<ManaTextArea value={fixedRows} onChange={setFixedRows} rows={5} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Character Count">
|
||||
<ManaTextArea value={charCount} onChange={setCharCount} showCharacterCount maxLength={200} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="States">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaTextArea value="Disabled textarea" disabled />
|
||||
<ManaTextArea value="Read only textarea" readOnly />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• value: string - Textarea value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (value: string) => void - Called on input change
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• placeholder?: string - Placeholder text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• rows?: number - Fixed number of rows
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• autosize?: boolean - Auto-grow with content
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• showCharacterCount?: boolean - Display character count
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• maxLength?: number - Maximum character limit
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable textarea
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• readOnly?: boolean - Make textarea read-only
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { ManaTextButton } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const VARIANTS = ["primary", "secondary", "always-white", "critical"] as const;
|
||||
|
||||
const TEXT_VARIANTS = [
|
||||
"text-xs/normal",
|
||||
"text-sm/normal",
|
||||
"text-sm/medium",
|
||||
"text-md/normal",
|
||||
"text-md/medium",
|
||||
"text-lg/medium",
|
||||
] as const;
|
||||
|
||||
export default function TextButtonTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Variants">
|
||||
<div className="vc-compfinder-grid">
|
||||
{VARIANTS.map(variant => (
|
||||
<ManaTextButton
|
||||
key={variant}
|
||||
text={variant}
|
||||
variant={variant}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Text Variants">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{TEXT_VARIANTS.map(textVariant => (
|
||||
<ManaTextButton
|
||||
key={textVariant}
|
||||
text={textVariant}
|
||||
textVariant={textVariant}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="States">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaTextButton text="Normal" />
|
||||
<ManaTextButton text="Disabled" disabled />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Line Clamp">
|
||||
<div className="vc-compfinder-grid" style={{ maxWidth: 200 }}>
|
||||
<ManaTextButton
|
||||
text="This is a very long text that should be clamped to one line"
|
||||
lineClamp={1}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• text - Button label text</Paragraph>
|
||||
<Paragraph color="text-muted">• variant - primary, secondary, always-white, critical</Paragraph>
|
||||
<Paragraph color="text-muted">• textVariant - Typography variant (e.g., text-sm/medium)</Paragraph>
|
||||
<Paragraph color="text-muted">• lineClamp - Max lines before truncation</Paragraph>
|
||||
<Paragraph color="text-muted">• disabled - Disable button</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick - Click handler</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaTextInput } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function TextInputTab() {
|
||||
const [basic, setBasic] = useState("Hello world");
|
||||
const [sizeSm, setSizeSm] = useState("Small input");
|
||||
const [sizeMd, setSizeMd] = useState("Medium input");
|
||||
const [withPlaceholder, setWithPlaceholder] = useState("");
|
||||
const [clearable, setClearable] = useState("Clear me");
|
||||
const [charCount, setCharCount] = useState("Count chars");
|
||||
const [error, setError] = useState("Invalid input");
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic">
|
||||
<ManaTextInput value={basic} onChange={setBasic} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Sizes">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaTextInput value={sizeSm} onChange={setSizeSm} size="sm" placeholder="Size: sm" />
|
||||
<ManaTextInput value={sizeMd} onChange={setSizeMd} size="md" placeholder="Size: md" />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Placeholder">
|
||||
<ManaTextInput value={withPlaceholder} onChange={setWithPlaceholder} placeholder="Enter text here..." />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Clearable">
|
||||
<ManaTextInput value={clearable} onChange={setClearable} clearable />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Character Count">
|
||||
<ManaTextInput value={charCount} onChange={setCharCount} showCharacterCount maxLength={50} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Error State">
|
||||
<ManaTextInput value={error} onChange={setError} error />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="States">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
<ManaTextInput value="Disabled" disabled />
|
||||
<ManaTextInput value="Read only" readOnly />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• value: string - Input value
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• onChange?: (value: string) => void - Called on input change
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• placeholder?: string - Placeholder text
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• size?: "sm" | "md" - Input size (default: "md")
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• clearable?: boolean - Show clear button
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• showCharacterCount?: boolean - Display character count
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• maxLength?: number - Maximum character limit
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• error?: boolean - Show error state styling
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• disabled?: boolean - Disable input
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• readOnly?: boolean - Make input read-only
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { ManaButton, ManaTooltip } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const POSITIONS = ["top", "bottom", "left", "right"] as const;
|
||||
const COLORS = ["primary", "black", "grey", "brand", "green", "yellow", "red"] as const;
|
||||
|
||||
export default function TooltipTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Positions">
|
||||
<div className="vc-compfinder-grid">
|
||||
{POSITIONS.map(position => (
|
||||
<ManaTooltip key={position} text={`Tooltip on ${position}`} position={position}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text={position} />
|
||||
)}
|
||||
</ManaTooltip>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Colors">
|
||||
<div className="vc-compfinder-grid">
|
||||
{COLORS.map(color => (
|
||||
<ManaTooltip key={color} text={`${color} tooltip`} color={color}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text={color} />
|
||||
)}
|
||||
</ManaTooltip>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Delay">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaTooltip text="No delay" delay={0}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text="No delay" />
|
||||
)}
|
||||
</ManaTooltip>
|
||||
<ManaTooltip text="500ms delay" delay={500}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text="500ms delay" />
|
||||
)}
|
||||
</ManaTooltip>
|
||||
<ManaTooltip text="1000ms delay" delay={1000}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text="1s delay" />
|
||||
)}
|
||||
</ManaTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Hide on Click">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaTooltip text="Hides when clicked" hideOnClick>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text="Click me" />
|
||||
)}
|
||||
</ManaTooltip>
|
||||
<ManaTooltip text="Stays visible when clicked" hideOnClick={false}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text="Click me" />
|
||||
)}
|
||||
</ManaTooltip>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• text: string - Tooltip content
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• children: (props) => ReactNode - Render function for trigger element
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• position?: "top" | "bottom" | "left" | "right" - Tooltip position
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• color?: "primary" | "black" | "grey" | "brand" | "green" | "yellow" | "red"
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• delay?: number - Show delay in milliseconds
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• hideOnClick?: boolean - Hide tooltip when trigger is clicked
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const TEXT_COLORS = [
|
||||
"text-default",
|
||||
"text-muted",
|
||||
"text-subtle",
|
||||
"text-strong",
|
||||
"text-normal",
|
||||
"text-link",
|
||||
"text-brand",
|
||||
"text-disabled",
|
||||
"text-invert",
|
||||
"text-feedback-critical",
|
||||
"text-feedback-warning",
|
||||
"text-feedback-info",
|
||||
"text-feedback-positive",
|
||||
"text-status-online",
|
||||
"text-status-idle",
|
||||
"text-status-dnd",
|
||||
"text-status-offline",
|
||||
"text-overlay-dark",
|
||||
"text-overlay-light",
|
||||
] as const;
|
||||
|
||||
const TEXT_SIZES = [
|
||||
{ name: "xxs", value: "0.625rem (10px)" },
|
||||
{ name: "xs", value: "0.75rem (12px)" },
|
||||
{ name: "sm", value: "0.875rem (14px)" },
|
||||
{ name: "md", value: "1rem (16px)" },
|
||||
{ name: "lg", value: "1.25rem (20px)" },
|
||||
{ name: "xl", value: "1.5rem (24px)" },
|
||||
{ name: "xxl", value: "2rem (32px)" },
|
||||
] as const;
|
||||
|
||||
const TEXT_WEIGHTS = [
|
||||
{ name: "thin", value: "100" },
|
||||
{ name: "extralight", value: "200" },
|
||||
{ name: "light", value: "300" },
|
||||
{ name: "normal", value: "400" },
|
||||
{ name: "medium", value: "500" },
|
||||
{ name: "semibold", value: "600" },
|
||||
{ name: "bold", value: "700" },
|
||||
{ name: "extrabold", value: "800" },
|
||||
] as const;
|
||||
|
||||
const FONT_SIZE_MAP: Record<string, string> = {
|
||||
xxs: "0.625rem",
|
||||
xs: "0.75rem",
|
||||
sm: "0.875rem",
|
||||
md: "1rem",
|
||||
lg: "1.25rem",
|
||||
xl: "1.5rem",
|
||||
xxl: "2rem",
|
||||
};
|
||||
|
||||
export default function TypographyTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Text Colors (CSS Variables)">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{TEXT_COLORS.map(color => (
|
||||
<div key={color} style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
||||
<span style={{ color: `var(--${color})`, minWidth: 200 }}>
|
||||
{color}
|
||||
</span>
|
||||
<code style={{ color: "var(--text-muted)", fontSize: "0.75rem" }}>
|
||||
var(--{color})
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Text Sizes">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{TEXT_SIZES.map(({ name, value }) => (
|
||||
<div key={name} style={{ display: "flex", alignItems: "baseline", gap: 16 }}>
|
||||
<span style={{ fontSize: FONT_SIZE_MAP[name], minWidth: 200 }}>
|
||||
{name}
|
||||
</span>
|
||||
<code style={{ color: "var(--text-muted)", fontSize: "0.75rem" }}>
|
||||
{value}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Text Weights">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{TEXT_WEIGHTS.map(({ name, value }) => (
|
||||
<div key={name} style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
||||
<span style={{ fontWeight: value, minWidth: 200 }}>
|
||||
{name}
|
||||
</span>
|
||||
<code style={{ color: "var(--text-muted)", fontSize: "0.75rem" }}>
|
||||
font-weight: {value}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Usage with BaseText">
|
||||
<div className="vc-compfinder-grid-vertical" style={{ gap: 4 }}>
|
||||
<code style={{ color: "var(--text-muted)", fontSize: "0.75rem" }}>
|
||||
{"import { BaseText } from \"@components/BaseText\";"}
|
||||
</code>
|
||||
<code style={{ color: "var(--text-muted)", fontSize: "0.75rem" }}>
|
||||
{"<BaseText size=\"md\" weight=\"semibold\" color=\"text-default\">Text</BaseText>"}
|
||||
</code>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Code Colors">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{[
|
||||
"text-code",
|
||||
"text-code-keyword",
|
||||
"text-code-string",
|
||||
"text-code-comment",
|
||||
"text-code-builtin",
|
||||
"text-code-variable",
|
||||
"text-code-tag",
|
||||
"text-code-title",
|
||||
"text-code-section",
|
||||
"text-code-bullet",
|
||||
"text-code-addition",
|
||||
"text-code-deletion",
|
||||
].map(color => (
|
||||
<div key={color} style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
||||
<span style={{ color: `var(--${color})`, minWidth: 200, fontFamily: "monospace" }}>
|
||||
{color}
|
||||
</span>
|
||||
<code style={{ color: "var(--text-muted)", fontSize: "0.75rem" }}>
|
||||
var(--{color})
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
<strong>BaseText</strong> - from @components/BaseText
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• size?: "xxs" | "xs" | "sm" | "md" | "lg" | "xl" | "xxl"
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• weight?: "thin" | "extralight" | "light" | "normal" | "medium" | "semibold" | "bold" | "extrabold"
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• color?: string - CSS variable name (e.g., "text-muted")
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• tag?: "span" | "p" | "div" | "h1" - etc.
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>Heading</strong> - from @components/Heading
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• tag?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" (default: h5)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>Paragraph</strong> - from @components/Paragraph
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• color?: string - CSS variable name
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 12 }}>
|
||||
<strong>Span</strong> - from @components/Span
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• Inline text with default styling
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
74
src/equicordplugins/components.dev/index.tsx
Normal file
74
src/equicordplugins/components.dev/index.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import SettingsPlugin from "@plugins/_core/settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { StartAt } from "@utils/types";
|
||||
import { openUserSettingsPanel } from "@webpack/common";
|
||||
|
||||
import ComponentsTab from "./components/ComponentsTab";
|
||||
|
||||
function ComponentsIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M3 15.5V6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v.5a.5.5 0 0 1-.5.5H17a4 4 0 0 0-4 4v4.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5ZM12.5 18H2a1 1 0 1 0 0 2h10.48c.33 0 .57-.3.54-.63A4.08 4.08 0 0 1 13 19v-.5a.5.5 0 0 0-.5-.5Z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M15 11c0-1.1.9-2 2-2h4a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-8Zm2 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "Components",
|
||||
description: "Adds a new tab to settings to browse Discord components.",
|
||||
authors: [EquicordDevs.Prism],
|
||||
dependencies: ["Settings"],
|
||||
startAt: StartAt.WebpackReady,
|
||||
toolboxActions: {
|
||||
"Open Components Tab"() {
|
||||
openUserSettingsPanel("equicord_components");
|
||||
},
|
||||
},
|
||||
start() {
|
||||
const { customEntries, customSections } = SettingsPlugin;
|
||||
|
||||
customEntries.push({
|
||||
key: "equicord_components",
|
||||
title: "Components",
|
||||
Component: ComponentsTab,
|
||||
Icon: ComponentsIcon
|
||||
});
|
||||
|
||||
customSections.push(() => ({
|
||||
section: "EquicordDiscordComponents",
|
||||
label: "Components",
|
||||
element: ComponentsTab,
|
||||
className: "vc-discord-components",
|
||||
id: "Components"
|
||||
}));
|
||||
},
|
||||
stop() {
|
||||
const { customEntries, customSections } = SettingsPlugin;
|
||||
const entryIdx = customEntries.findIndex(e => e.key === "equicord_components");
|
||||
const sectionIdx = customSections.findIndex(s => s({} as any).id === "Components");
|
||||
if (entryIdx !== -1) customEntries.splice(entryIdx, 1);
|
||||
if (sectionIdx !== -1) customSections.splice(sectionIdx, 1);
|
||||
},
|
||||
});
|
||||
89
src/equicordplugins/components.dev/styles.css
Normal file
89
src/equicordplugins/components.dev/styles.css
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
.vc-compfinder-tabbar {
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap !important;
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
.vc-compfinder-tabbar > div {
|
||||
flex-wrap: wrap !important;
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
.vc-compfinder-tab {
|
||||
padding: 8px 12px;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.vc-compfinder-content {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.vc-compfinder-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vc-compfinder-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vc-compfinder-grid-vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vc-compfinder-switch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.vc-compfinder-demo-button {
|
||||
padding: 8px 16px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 4px;
|
||||
color: var(--text-normal);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vc-compfinder-demo-button:hover {
|
||||
background: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
.vc-compfinder-modal-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vc-compfinder-modal-header-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.vc-compfinder-modal-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.vc-compfinder-modal-content {
|
||||
padding-bottom: var(--space-16);
|
||||
}
|
||||
|
||||
.vc-compfinder-modal-footer {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 8px;
|
||||
}
|
||||
19
src/equicordplugins/components.dev/types/anchor.d.ts
vendored
Normal file
19
src/equicordplugins/components.dev/types/anchor.d.ts
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface AnchorProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
href?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
rel?: string;
|
||||
target?: string;
|
||||
useDefaultUnderlineStyles?: boolean;
|
||||
title?: string;
|
||||
style?: React.CSSProperties;
|
||||
focusProps?: Record<string, unknown>;
|
||||
ref?: React.Ref<HTMLAnchorElement>;
|
||||
}
|
||||
25
src/equicordplugins/components.dev/types/avatar.d.ts
vendored
Normal file
25
src/equicordplugins/components.dev/types/avatar.d.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type AvatarSize = "SIZE_16" | "SIZE_20" | "SIZE_24" | "SIZE_32" | "SIZE_40" | "SIZE_48" | "SIZE_56" | "SIZE_80" | "SIZE_120";
|
||||
|
||||
export type AvatarStatus = "online" | "idle" | "dnd" | "offline" | "streaming";
|
||||
|
||||
export interface AvatarProps {
|
||||
src?: string;
|
||||
size?: AvatarSize;
|
||||
status?: AvatarStatus;
|
||||
statusColor?: string;
|
||||
isMobile?: boolean;
|
||||
isTyping?: boolean;
|
||||
isSpeaking?: boolean;
|
||||
statusTooltip?: boolean;
|
||||
statusTooltipDelay?: number;
|
||||
"aria-hidden"?: boolean;
|
||||
"aria-label"?: string;
|
||||
imageClassName?: string;
|
||||
className?: string;
|
||||
}
|
||||
48
src/equicordplugins/components.dev/types/badge.d.ts
vendored
Normal file
48
src/equicordplugins/components.dev/types/badge.d.ts
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface BadgeShapesType {
|
||||
ROUND: string;
|
||||
ROUND_LEFT: string;
|
||||
ROUND_RIGHT: string;
|
||||
SQUARE: string;
|
||||
}
|
||||
|
||||
export interface NumberBadgeProps {
|
||||
count: number;
|
||||
maxDigits?: number;
|
||||
color?: string;
|
||||
disableColor?: boolean;
|
||||
shape?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface TextBadgeProps {
|
||||
text: string;
|
||||
color?: string;
|
||||
disableColor?: boolean;
|
||||
shape?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface IconBadgeProps {
|
||||
icon: React.ComponentType<{ className?: string; color?: string; }>;
|
||||
color?: string;
|
||||
disableColor?: boolean;
|
||||
shape?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface CircleBadgeProps {
|
||||
color?: string;
|
||||
disableColor?: boolean;
|
||||
shape?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
29
src/equicordplugins/components.dev/types/button.d.ts
vendored
Normal file
29
src/equicordplugins/components.dev/types/button.d.ts
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ManaButtonProps {
|
||||
text?: string;
|
||||
variant?: "primary" | "secondary" | "critical-primary" | "critical-secondary" | "overlay-primary" | "overlay-secondary" | "expressive";
|
||||
size?: "xs" | "sm" | "md";
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
fullWidth?: boolean;
|
||||
rounded?: boolean;
|
||||
icon?: React.ComponentType<any>;
|
||||
iconPosition?: "start" | "end";
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface ManaTextButtonProps {
|
||||
text: string;
|
||||
variant?: "primary" | "secondary" | "always-white" | "critical";
|
||||
textVariant?: string;
|
||||
lineClamp?: number;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
32
src/equicordplugins/components.dev/types/calendar.d.ts
vendored
Normal file
32
src/equicordplugins/components.dev/types/calendar.d.ts
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ManaCalendarProps {
|
||||
value?: any;
|
||||
onChange?: (value: any) => void;
|
||||
minValue?: any;
|
||||
maxValue?: any;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
className?: string;
|
||||
"aria-label"?: string;
|
||||
}
|
||||
|
||||
export interface ManaDatePickerProps {
|
||||
value?: any;
|
||||
onChange?: (value: any) => void;
|
||||
minValue?: any;
|
||||
maxValue?: any;
|
||||
placeholderValue?: any;
|
||||
granularity?: "day" | "hour" | "minute" | "second";
|
||||
hourCycle?: 12 | 24;
|
||||
hideTimeZone?: boolean;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
label?: string;
|
||||
description?: string;
|
||||
errorMessage?: string;
|
||||
}
|
||||
32
src/equicordplugins/components.dev/types/checkbox.d.ts
vendored
Normal file
32
src/equicordplugins/components.dev/types/checkbox.d.ts
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ManaCheckboxProps {
|
||||
checked: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
displayOnly?: boolean;
|
||||
label?: string;
|
||||
description?: string;
|
||||
leadingIcon?: React.ComponentType<any>;
|
||||
labelType?: "primary" | "secondary";
|
||||
usageVariant?: "single" | "indicator";
|
||||
}
|
||||
|
||||
export interface CheckboxGroupOption {
|
||||
value: string | number;
|
||||
label: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
leadingIcon?: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
export interface ManaCheckboxGroupProps {
|
||||
options: CheckboxGroupOption[];
|
||||
selectedValues: (string | number)[];
|
||||
onChange?: (values: (string | number)[]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
19
src/equicordplugins/components.dev/types/clickable.d.ts
vendored
Normal file
19
src/equicordplugins/components.dev/types/clickable.d.ts
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ClickableProps extends React.HTMLAttributes<HTMLElement> {
|
||||
tag?: keyof React.JSX.IntrinsicElements;
|
||||
focusProps?: Record<string, unknown>;
|
||||
innerRef?: React.Ref<HTMLElement>;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
role?: string;
|
||||
tabIndex?: number;
|
||||
ignoreKeyPress?: boolean;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
href?: string;
|
||||
onKeyPress?: React.KeyboardEventHandler<HTMLElement>;
|
||||
}
|
||||
77
src/equicordplugins/components.dev/types/colorpicker.d.ts
vendored
Normal file
77
src/equicordplugins/components.dev/types/colorpicker.d.ts
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ColorPickerProps {
|
||||
color: number;
|
||||
onChange: (color: number) => void;
|
||||
onClose?: () => void;
|
||||
suggestedColors?: string[];
|
||||
disabled?: boolean;
|
||||
label?: React.ReactNode;
|
||||
colorPickerMiddle?: React.ReactNode;
|
||||
colorPickerFooter?: React.ReactNode;
|
||||
showEyeDropper?: boolean;
|
||||
}
|
||||
|
||||
export interface ColorSwatchProps {
|
||||
color?: number;
|
||||
isDefault?: boolean;
|
||||
isCustom?: boolean;
|
||||
isSelected?: boolean;
|
||||
disabled?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
onClick?: (colorOrGradient: number | { start: number; end: number; }) => void;
|
||||
isGradient?: boolean;
|
||||
"aria-label"?: string;
|
||||
gradientStart?: number;
|
||||
gradientEnd?: number;
|
||||
gradientDegrees?: number;
|
||||
}
|
||||
|
||||
export interface DefaultColorButtonProps {
|
||||
color: number;
|
||||
onChange: (color: number) => void;
|
||||
value: number;
|
||||
disabled?: boolean;
|
||||
allowBlackCustomColor?: boolean;
|
||||
}
|
||||
|
||||
export interface CustomColorButtonProps {
|
||||
customColor?: number;
|
||||
value: number;
|
||||
disabled?: boolean;
|
||||
"aria-label"?: string;
|
||||
presets: number[];
|
||||
}
|
||||
|
||||
export interface GradientColor {
|
||||
start: number;
|
||||
end: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface ColorPickerWithSwatchesProps {
|
||||
className?: string;
|
||||
defaultColor: number;
|
||||
customColor?: number;
|
||||
colors: number[] | GradientColor[];
|
||||
value: number;
|
||||
secondaryValue?: number;
|
||||
disabled?: boolean;
|
||||
onChange: (color: number, secondaryColor?: number) => void;
|
||||
renderDefaultButton?: (props: DefaultColorButtonProps) => React.ReactNode;
|
||||
renderCustomButton?: (props: CustomColorButtonProps) => React.ReactNode;
|
||||
colorContainerClassName?: string;
|
||||
isGradient?: boolean;
|
||||
renderGradientCustomButton?: (props: {
|
||||
value: number;
|
||||
startColor: number;
|
||||
endColor: number;
|
||||
disabled?: boolean;
|
||||
}) => React.ReactNode;
|
||||
gradientDegrees?: number;
|
||||
allowBlackCustomColor?: boolean;
|
||||
}
|
||||
25
src/equicordplugins/components.dev/types/index.ts
Normal file
25
src/equicordplugins/components.dev/types/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export * from "./anchor";
|
||||
export * from "./avatar";
|
||||
export * from "./badge";
|
||||
export * from "./button";
|
||||
export * from "./calendar";
|
||||
export * from "./checkbox";
|
||||
export * from "./clickable";
|
||||
export * from "./colorpicker";
|
||||
export * from "./input";
|
||||
export * from "./notice";
|
||||
export * from "./paginator";
|
||||
export * from "./progress";
|
||||
export * from "./radio";
|
||||
export * from "./select";
|
||||
export * from "./slider";
|
||||
export * from "./spinner";
|
||||
export * from "./switch";
|
||||
export * from "./tabbar";
|
||||
export * from "./tooltip";
|
||||
46
src/equicordplugins/components.dev/types/input.d.ts
vendored
Normal file
46
src/equicordplugins/components.dev/types/input.d.ts
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ManaTextInputProps {
|
||||
value: string;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
size?: "sm" | "md";
|
||||
error?: boolean;
|
||||
clearable?: boolean;
|
||||
showCharacterCount?: boolean;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
export interface ManaTextAreaProps {
|
||||
value: string;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
rows?: number;
|
||||
autosize?: boolean;
|
||||
maxLength?: number;
|
||||
showCharacterCount?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchBarProps {
|
||||
query: string;
|
||||
onChange: (value: string) => void;
|
||||
onClear?: () => void;
|
||||
placeholder?: string;
|
||||
size?: "sm" | "md";
|
||||
autoFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
onKeyDown?: (e: React.KeyboardEvent) => void;
|
||||
onBlur?: () => void;
|
||||
onFocus?: () => void;
|
||||
autoComplete?: string;
|
||||
inputProps?: Record<string, any>;
|
||||
"aria-label"?: string;
|
||||
}
|
||||
18
src/equicordplugins/components.dev/types/notice.d.ts
vendored
Normal file
18
src/equicordplugins/components.dev/types/notice.d.ts
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type NoticeType = "warn" | "info" | "danger" | "positive" | "preview";
|
||||
|
||||
export interface NoticeProps {
|
||||
children: React.ReactNode;
|
||||
messageType: NoticeType;
|
||||
action?: React.ReactNode;
|
||||
className?: string;
|
||||
textColor?: string;
|
||||
textVariant?: string;
|
||||
icon?: React.ComponentType<{ className?: string; color?: string; }>;
|
||||
hidden?: boolean;
|
||||
}
|
||||
24
src/equicordplugins/components.dev/types/paginator.d.ts
vendored
Normal file
24
src/equicordplugins/components.dev/types/paginator.d.ts
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface PaginatorPageWrapperProps {
|
||||
key: string;
|
||||
targetPage: number;
|
||||
selected: boolean;
|
||||
navigateToPage: () => void;
|
||||
}
|
||||
|
||||
export interface PaginatorProps {
|
||||
currentPage: number;
|
||||
totalCount: number;
|
||||
pageSize: number;
|
||||
maxVisiblePages?: number;
|
||||
disablePaginationGap?: boolean;
|
||||
onPageChange?: (page: number) => void;
|
||||
hideMaxPage?: boolean;
|
||||
className?: string;
|
||||
renderPageWrapper?: (props: PaginatorPageWrapperProps, defaultRender: React.ReactNode) => React.ReactNode;
|
||||
}
|
||||
25
src/equicordplugins/components.dev/types/progress.d.ts
vendored
Normal file
25
src/equicordplugins/components.dev/types/progress.d.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type ProgressBarVariant = "blue" | "orange" | "unset";
|
||||
|
||||
export interface ProgressBarOverride {
|
||||
background?: string;
|
||||
gradientStart?: string;
|
||||
gradientEnd?: string;
|
||||
}
|
||||
|
||||
export interface ProgressBarProps {
|
||||
progress: number;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
variant?: ProgressBarVariant;
|
||||
override?: {
|
||||
default?: ProgressBarOverride;
|
||||
[key: string]: ProgressBarOverride | undefined;
|
||||
};
|
||||
labelledBy?: string;
|
||||
}
|
||||
26
src/equicordplugins/components.dev/types/radio.d.ts
vendored
Normal file
26
src/equicordplugins/components.dev/types/radio.d.ts
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface RadioOption {
|
||||
value: string | number;
|
||||
name: string;
|
||||
desc?: string;
|
||||
disabled?: boolean;
|
||||
leadingIcon?: React.ComponentType<{ className?: string; size?: string; color?: string; }>;
|
||||
}
|
||||
|
||||
export interface ManaBaseRadioGroupProps {
|
||||
options: RadioOption[];
|
||||
value?: string | number;
|
||||
onChange?: (value: string | number) => void;
|
||||
disabled?: boolean;
|
||||
"aria-labelledby"?: string;
|
||||
}
|
||||
|
||||
export interface StandaloneRadioIndicatorProps {
|
||||
isSelected: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
66
src/equicordplugins/components.dev/types/select.d.ts
vendored
Normal file
66
src/equicordplugins/components.dev/types/select.d.ts
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ManaSelectOption {
|
||||
id: string;
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface ManaSelectProps {
|
||||
options: ManaSelectOption[];
|
||||
value?: string | string[] | null;
|
||||
onSelectionChange?: (value: string | string[] | null) => void;
|
||||
selectionMode?: "single" | "multiple";
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
clearable?: boolean;
|
||||
fullWidth?: boolean;
|
||||
autoFocus?: boolean;
|
||||
closeOnSelect?: boolean;
|
||||
shouldFocusWrap?: boolean;
|
||||
maxOptionsVisible?: number;
|
||||
wrapTags?: boolean;
|
||||
}
|
||||
|
||||
export interface ManaComboboxProps {
|
||||
placeholder?: string;
|
||||
value?: string | string[];
|
||||
onChange?: (value: string | string[]) => void;
|
||||
multiSelect?: boolean;
|
||||
autoFocus?: boolean;
|
||||
maxVisibleItems?: number;
|
||||
emptyStateText?: string;
|
||||
emptyStateHeader?: string;
|
||||
onQueryChange?: (query: string) => void;
|
||||
"aria-label"?: string;
|
||||
className?: string;
|
||||
listClassName?: string;
|
||||
children: (query: string) => React.ReactNode[];
|
||||
}
|
||||
|
||||
export interface ListboxItem {
|
||||
id: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ManaListboxProps {
|
||||
items: ListboxItem[];
|
||||
selectedItems?: ListboxItem[];
|
||||
defaultSelectedItems?: ListboxItem[];
|
||||
onSelectionChange?: (items: ListboxItem[]) => void;
|
||||
selectionMode?: "single" | "multiple";
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
loading?: boolean;
|
||||
maxVisibleItems?: number;
|
||||
shouldFocusWrap?: boolean;
|
||||
typeahead?: boolean;
|
||||
renderListItem?: (item: ListboxItem) => React.ReactNode;
|
||||
renderEmptyState?: () => React.ReactNode;
|
||||
}
|
||||
29
src/equicordplugins/components.dev/types/slider.d.ts
vendored
Normal file
29
src/equicordplugins/components.dev/types/slider.d.ts
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface SliderProps {
|
||||
initialValue?: number;
|
||||
value?: number;
|
||||
minValue?: number;
|
||||
maxValue?: number;
|
||||
onValueChange?: (value: number) => void;
|
||||
onValueRender?: (value: number) => string;
|
||||
markers?: number[];
|
||||
stickToMarkers?: boolean;
|
||||
onMarkerRender?: (value: number) => string;
|
||||
renderMarker?: (value: number) => React.ReactNode;
|
||||
disabled?: boolean;
|
||||
mini?: boolean;
|
||||
hideBubble?: boolean;
|
||||
defaultValue?: number;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
barClassName?: string;
|
||||
grabberClassName?: string;
|
||||
className?: string;
|
||||
"aria-label"?: string;
|
||||
"aria-labelledby"?: string;
|
||||
"aria-describedby"?: string;
|
||||
}
|
||||
15
src/equicordplugins/components.dev/types/spinner.d.ts
vendored
Normal file
15
src/equicordplugins/components.dev/types/spinner.d.ts
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type SpinnerType = "wanderingCubes" | "chasingDots" | "pulsingEllipsis" | "spinningCircle" | "spinningCircleSimple" | "lowMotion";
|
||||
|
||||
export interface SpinnerProps {
|
||||
type?: SpinnerType;
|
||||
animated?: boolean;
|
||||
className?: string;
|
||||
itemClassName?: string;
|
||||
"aria-label"?: string;
|
||||
}
|
||||
11
src/equicordplugins/components.dev/types/switch.d.ts
vendored
Normal file
11
src/equicordplugins/components.dev/types/switch.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ManaSwitchProps {
|
||||
checked: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
55
src/equicordplugins/components.dev/types/tabbar.d.ts
vendored
Normal file
55
src/equicordplugins/components.dev/types/tabbar.d.ts
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface TabBarProps {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
type?: "side" | "top" | "top-pill";
|
||||
look?: "brand" | "grey";
|
||||
style?: React.CSSProperties;
|
||||
selectedItem?: string;
|
||||
onItemSelect?: (id: string) => void;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
"aria-label"?: string;
|
||||
}
|
||||
|
||||
export interface TabBarItemProps {
|
||||
id: string;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
selectedItem?: string;
|
||||
onItemSelect?: (id: string) => void;
|
||||
itemType?: "side" | "top" | "top-pill";
|
||||
look?: "brand" | "grey";
|
||||
color?: string;
|
||||
variant?: "destructive";
|
||||
disabled?: boolean;
|
||||
disableItemStyles?: boolean;
|
||||
onClick?: React.MouseEventHandler;
|
||||
onContextMenu?: React.MouseEventHandler;
|
||||
clickableRef?: React.Ref<HTMLElement>;
|
||||
clickableInnerRef?: React.Ref<HTMLElement>;
|
||||
"aria-label"?: string;
|
||||
}
|
||||
|
||||
export interface TabBarHeaderProps {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
onClick?: React.MouseEventHandler;
|
||||
"aria-expanded"?: boolean;
|
||||
"aria-controls"?: string;
|
||||
}
|
||||
|
||||
export interface TabBarSeparatorProps {
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export type TabBarComponent = React.ComponentType<TabBarProps> & {
|
||||
Item: React.ComponentType<TabBarItemProps>;
|
||||
Header: React.ComponentType<TabBarHeaderProps>;
|
||||
Separator: React.ComponentType<TabBarSeparatorProps>;
|
||||
Panel: React.ComponentType<{ children?: React.ReactNode; }>;
|
||||
};
|
||||
53
src/equicordplugins/components.dev/types/tooltip.d.ts
vendored
Normal file
53
src/equicordplugins/components.dev/types/tooltip.d.ts
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface ManaTooltipProps {
|
||||
text: string;
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
align?: "start" | "center" | "end";
|
||||
color?: "primary" | "black" | "grey" | "brand" | "green" | "yellow" | "red";
|
||||
spacing?: number;
|
||||
hideOnClick?: boolean;
|
||||
delay?: number;
|
||||
children: (props: any) => React.ReactNode;
|
||||
}
|
||||
|
||||
export interface ManaRichTooltipProps {
|
||||
title?: string;
|
||||
body: string;
|
||||
asset?: React.ReactNode | { src: string; alt?: string; };
|
||||
assetSize?: number;
|
||||
asContainer?: boolean;
|
||||
element?: "span" | "div";
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
align?: "start" | "center" | "end";
|
||||
spacing?: number;
|
||||
ariaHidden?: boolean;
|
||||
children: React.ReactNode | ((props: any) => React.ReactNode);
|
||||
}
|
||||
|
||||
export interface PopoverAction {
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
variant?: "primary" | "secondary";
|
||||
}
|
||||
|
||||
export interface ManaPopoverProps {
|
||||
title?: string;
|
||||
body?: string;
|
||||
badge?: React.ReactNode;
|
||||
graphic?: { src: string; aspectRatio?: string; };
|
||||
size?: "sm" | "md" | "lg";
|
||||
actions?: PopoverAction[];
|
||||
textLink?: { text: string; onClick?: () => void; };
|
||||
gradientColor?: string;
|
||||
onRequestClose?: (reason: string) => void;
|
||||
targetElementRef: React.RefObject<HTMLElement | null>;
|
||||
shouldShow: boolean;
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
alignmentStrategy?: "edge" | "trigger-center";
|
||||
align?: "left" | "center" | "right";
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Channel, User, VoiceState } from "@vencord/discord-types";
|
||||
|
|
@ -78,9 +78,9 @@ export default definePlugin({
|
|||
authors: [EquicordDevs.TheArmagan],
|
||||
settings,
|
||||
settingsAboutComponent: () => (
|
||||
<Alert.Info>
|
||||
<Notice.Info>
|
||||
This Plugin is used to follow a Friend/Friends into voice chat(s).
|
||||
</Alert.Info>
|
||||
</Notice.Info>
|
||||
),
|
||||
flux: {
|
||||
async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
|
|
@ -25,9 +25,9 @@ export default definePlugin({
|
|||
description: "Prevents in-call/PiP previews (screenshare, streams, etc) from pausing even if the client loses focus",
|
||||
authors: [EquicordDevs.vappstar],
|
||||
settingsAboutComponent: () => (
|
||||
<Alert.Warning>
|
||||
<Notice.Warning>
|
||||
This plugin will cause discord to use more resources than normal
|
||||
</Alert.Warning>
|
||||
</Notice.Warning>
|
||||
),
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
|
@ -50,9 +50,9 @@ export default definePlugin({
|
|||
description: "Spoof what platform or device you're on",
|
||||
authors: [EquicordDevs.Drag],
|
||||
settingsAboutComponent: () => (
|
||||
<Alert.Warning>
|
||||
<Notice.Warning>
|
||||
We can't guarantee this plugin won't get you warned or banned.
|
||||
</Alert.Warning>
|
||||
</Notice.Warning>
|
||||
),
|
||||
settings: settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import "./style.css";
|
|||
import { HeaderBarButton } from "@api/HeaderBar";
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getTheme, Theme } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -220,9 +220,9 @@ export default definePlugin({
|
|||
authors: [Devs.amia],
|
||||
settings,
|
||||
settingsAboutComponent: () => (
|
||||
<Alert.Info>
|
||||
<Notice.Info>
|
||||
You must manually accept the quest first before clicking the button.
|
||||
</Alert.Info>
|
||||
</Notice.Info>
|
||||
),
|
||||
headerBarButton: {
|
||||
icon: QuestIcon,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
|
|
@ -12,9 +12,9 @@ export default definePlugin({
|
|||
name: "QuestFocused",
|
||||
description: "Prevent the quests player from pausing and possibly skip it all together.",
|
||||
settingsAboutComponent: () => (
|
||||
<Alert.Warning>
|
||||
<Notice.Warning>
|
||||
You might need to spam left mouse button on the video to skip it.
|
||||
</Alert.Warning>
|
||||
</Notice.Warning>
|
||||
),
|
||||
authors: [EquicordDevs.secp192k1],
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Alert } from "@components/Alert";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { User } from "@vencord/discord-types";
|
||||
|
|
@ -41,9 +41,9 @@ export default definePlugin({
|
|||
description: "Mention any user, regardless of channel access.",
|
||||
settings,
|
||||
settingsAboutComponent: () => (
|
||||
<Alert.Warning>
|
||||
<Notice.Warning>
|
||||
Using Global Mention can cause performance issues and show an absurd amount of users in the autocomplete.
|
||||
</Alert.Warning>
|
||||
</Notice.Warning>
|
||||
),
|
||||
|
||||
patches: [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue