Components: improvements and changes (#586)
* Components: add Timestamp, Chip, Skeleton, Accordion tabs and fix Spinner * yeet deprecated backwards-compatibility code * remove . * t * bunch of fixes and new components * expand types * add missing * this can be undefined? * t * Update MediaEngineStore.d.ts * Update ComponentsTab.tsx --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
parent
daca068537
commit
ea972836d3
65 changed files with 2293 additions and 557 deletions
|
|
@ -1,18 +1,47 @@
|
|||
import { FluxStore } from "..";
|
||||
|
||||
type VideoDevice = {
|
||||
containerId: string;
|
||||
disabled: boolean;
|
||||
effects: any;
|
||||
facing: string;
|
||||
guid: string;
|
||||
hardwareId: string;
|
||||
export interface AudioDevice {
|
||||
id: string;
|
||||
index: number;
|
||||
name: string;
|
||||
};
|
||||
disabled: boolean;
|
||||
facing?: string;
|
||||
guid: string;
|
||||
hardwareId: string;
|
||||
containerId: string;
|
||||
effects?: unknown;
|
||||
}
|
||||
|
||||
type GoLiveSource = {
|
||||
export interface VideoDevice {
|
||||
id: string;
|
||||
index: number;
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
facing?: string;
|
||||
guid: string;
|
||||
hardwareId?: string;
|
||||
containerId?: string;
|
||||
effects?: unknown;
|
||||
}
|
||||
|
||||
export interface ClipsSource {
|
||||
quality: {
|
||||
frameRate: number;
|
||||
resolution: number;
|
||||
};
|
||||
desktopDescription: {
|
||||
id: string;
|
||||
soundshareId: number;
|
||||
useLoopback: boolean;
|
||||
useVideoHook: boolean;
|
||||
useGraphicsCapture: boolean;
|
||||
useQuartzCapturer: boolean;
|
||||
allowScreenCaptureKit: boolean;
|
||||
hdrCaptureMode: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GoLiveSource {
|
||||
desktopSource: {
|
||||
id: string;
|
||||
sourcePid: number | null;
|
||||
|
|
@ -23,38 +52,169 @@ type GoLiveSource = {
|
|||
resolution: number;
|
||||
frameRate: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
type CodecCapabilities = (codecs: string) => void;
|
||||
export interface VideoStreamParameter {
|
||||
rid: string;
|
||||
type: string;
|
||||
quality: number;
|
||||
}
|
||||
|
||||
interface MediaEngine {
|
||||
export interface LocalPan {
|
||||
left: number;
|
||||
right: number;
|
||||
}
|
||||
|
||||
export interface ModeOptions {
|
||||
threshold: number;
|
||||
autoThreshold: boolean;
|
||||
vadUseKrisp: boolean;
|
||||
vadKrispActivationThreshold: number;
|
||||
vadLeading: number;
|
||||
vadTrailing: number;
|
||||
delay: number;
|
||||
shortcut: string[];
|
||||
vadDuringPreProcess?: boolean;
|
||||
}
|
||||
|
||||
export interface LoopbackOptions {
|
||||
echoCancellation: boolean;
|
||||
noiseSuppression: boolean;
|
||||
automaticGainControl: boolean;
|
||||
noiseCancellation: boolean;
|
||||
}
|
||||
|
||||
export type MediaEngineEvent =
|
||||
| "Destroy"
|
||||
| "Silence"
|
||||
| "Connection"
|
||||
| "DeviceChange"
|
||||
| "VolumeChange"
|
||||
| "VoiceActivity"
|
||||
| "WatchdogTimeout"
|
||||
| "AudioPermission"
|
||||
| "VideoPermission"
|
||||
| "DesktopSourceEnd"
|
||||
| "ConnectionStats"
|
||||
| "VideoInputInitialized"
|
||||
| "AudioInputInitialized"
|
||||
| "ClipsRecordingRestartNeeded"
|
||||
| "ClipsInitFailure"
|
||||
| "ClipsRecordingEnded"
|
||||
| "NativeScreenSharePickerUpdate"
|
||||
| "NativeScreenSharePickerCancel"
|
||||
| "NativeScreenSharePickerError"
|
||||
| "AudioDeviceModuleError"
|
||||
| "VoiceFiltersFailed"
|
||||
| "VideoCodecError"
|
||||
| "VoiceQueueMetrics"
|
||||
| "SystemMicrophoneModeChange"
|
||||
| "SelectedDeviceChange";
|
||||
|
||||
export type MediaEngineContext = "default" | "stream";
|
||||
|
||||
export interface MediaEngine {
|
||||
Camera: React.ComponentType<{ disabled?: boolean; deviceId?: string; width?: number; height?: number; }>;
|
||||
Video: React.ComponentType & { onContainerResized: () => void; };
|
||||
|
||||
on(event: MediaEngineEvent, listener: (...args: unknown[]) => void): this;
|
||||
once(event: MediaEngineEvent, listener: (...args: unknown[]) => void): this;
|
||||
off(event: MediaEngineEvent, listener: (...args: unknown[]) => void): this;
|
||||
emit(event: MediaEngineEvent, ...args: unknown[]): boolean;
|
||||
removeAllListeners(event?: MediaEngineEvent): this;
|
||||
listenerCount(event: MediaEngineEvent): number;
|
||||
|
||||
applyMediaFilterSettings(settings: unknown): Promise<void>;
|
||||
connect(userId: string, channelId: string, options: unknown): unknown;
|
||||
connectionsEmpty(): boolean;
|
||||
getAudioLayer(): boolean;
|
||||
getCodecCapabilities(callback: (CodecCapabilities: string) => void): string;
|
||||
createReplayConnection(userId: string, options: unknown): unknown;
|
||||
destroy(): void;
|
||||
eachConnection(callback: (connection: unknown) => void, context?: string): void;
|
||||
enable(): Promise<void>;
|
||||
exportClip(clipId: string, userId: string): Promise<Blob>;
|
||||
fetchAsyncResources(options: { fetchDave?: boolean; }): Promise<void>;
|
||||
|
||||
getAudioInputDevices(): AudioDevice[];
|
||||
getAudioLayer(): string;
|
||||
getAudioOutputDevices(): AudioDevice[];
|
||||
getAudioSubsystem(): string;
|
||||
getCodecCapabilities(callback: (capabilities: string) => void): void;
|
||||
getCodecSurvey(): Promise<unknown>;
|
||||
getDebugLogging(): boolean;
|
||||
getDesktopSource(): Promise<unknown>;
|
||||
getLoopback(): boolean;
|
||||
getMLSSigningKey(userId: string, guildId: string): Promise<{ key: unknown; signature: unknown; }>;
|
||||
getNoiseCancellationStats(): Promise<unknown>;
|
||||
getScreenPreviews(width: number, height: number): Promise<unknown[]>;
|
||||
getSupportedBandwidthEstimationExperiments(callback: (experiments: unknown) => void): void;
|
||||
getSupportedSecureFramesProtocolVersion(): number;
|
||||
getSupportedVideoCodecs(callback: (codecs: string[]) => void): void;
|
||||
getSystemMicrophoneMode(): Promise<string>;
|
||||
getVideoInputDeviceId(): string;
|
||||
getVideoInputDevices(): VideoDevice[];
|
||||
getWindowPreviews(width: number, height: number): Promise<unknown[]>;
|
||||
|
||||
interact(): void;
|
||||
presentNativeScreenSharePicker(options?: string): void;
|
||||
queueAudioSubsystem(subsystem: string): void;
|
||||
rankRtcRegions(regions: string[]): Promise<string[]>;
|
||||
releaseNativeDesktopVideoSourcePickerStream(): void;
|
||||
|
||||
saveClip(clipId: string, userId: string): Promise<unknown>;
|
||||
saveClipForUser(clipId: string, userId: string, options: unknown): Promise<unknown>;
|
||||
saveScreenshot(channelId: string, userId: string, width: number | null, height: number | null, options: unknown): Promise<unknown>;
|
||||
|
||||
setAecDump(value: boolean): void;
|
||||
setAsyncClipsSourceDeinit(callback: () => void): void;
|
||||
setAsyncVideoInputDeviceInit(callback: () => void): void;
|
||||
setAudioInputBypassSystemProcessing(value: boolean): void;
|
||||
setAudioInputDevice(deviceId: string): void;
|
||||
setAudioOutputDevice(deviceId: string): void;
|
||||
setAudioSubsystem(subsystem: string): void;
|
||||
setAv1Enabled(value: boolean): void;
|
||||
setClipBufferLength(length: number): void;
|
||||
setClipsMLPipelineEnabled(value: boolean): void;
|
||||
setClipsMLPipelineTypeEnabled(type: string, enabled: boolean): void;
|
||||
setClipsQualitySettings(resolution: number, frameRate: number, hdr: boolean): boolean;
|
||||
setClipsSource(source: ClipsSource | null): void;
|
||||
setDebugLogging(value: boolean): void;
|
||||
setH265Enabled(value: boolean): void;
|
||||
setGoLiveSource(source: GoLiveSource | null, context?: string): void;
|
||||
setH264Enabled(value: boolean): void;
|
||||
setH265Enabled(value: boolean): void;
|
||||
setHasFullbandPerformance(value: boolean): void;
|
||||
setInputVolume(volume: number): void;
|
||||
setLoopback(reason: string, enabled: boolean): void;
|
||||
setLoopback(reason: string, options: LoopbackOptions): void;
|
||||
setMaxSyncDelayOverride(delay: number): void;
|
||||
setMaybePreprocessMute(value: boolean): void;
|
||||
setNativeDesktopVideoSourcePickerActive(value: boolean): void;
|
||||
setNoiseCancellationEnableStats(value: boolean): void;
|
||||
setOffloadAdmControls(value: boolean): void;
|
||||
setOnVideoContainerResized(callback: () => void): void;
|
||||
setOutputVolume(volume: number): void;
|
||||
setSidechainCompression(value: boolean): void;
|
||||
setSidechainCompressionStrength(strength: number): void;
|
||||
setVideoInputDevice(deviceId: string): void;
|
||||
setSoundshareSource(soundshareId: number, enabled: boolean, context?: string): void;
|
||||
setVideoInputDevice(deviceId: string): Promise<void>;
|
||||
|
||||
shouldConnectionBroadcastVideo(connection: unknown): boolean;
|
||||
showSystemCaptureConfigurationUI(options: unknown): void;
|
||||
|
||||
startAecDump(): void;
|
||||
startLocalAudioRecording(options: unknown): Promise<void>;
|
||||
startRecordingRawSamples(options: unknown): void;
|
||||
stopAecDump(): void;
|
||||
stopLocalAudioRecording(): void;
|
||||
stopLocalAudioRecording(callback: (success: boolean, data: unknown) => void): void;
|
||||
stopRecordingRawSamples(): void;
|
||||
|
||||
supported(): boolean;
|
||||
supports(value: string): boolean;
|
||||
writeAudioDebugState(): Promise<never>;
|
||||
supports(feature: string): boolean;
|
||||
updateClipMetadata(clipId: string, metadata: unknown): Promise<void>;
|
||||
watchdogTick(): void;
|
||||
writeAudioDebugState(): Promise<void>;
|
||||
}
|
||||
|
||||
export class MediaEngineStore extends FluxStore {
|
||||
fetchAsyncResources(): void;
|
||||
getActiveInputProfile(): string;
|
||||
getActiveVoiceFilter(): string | null;
|
||||
getActiveVoiceFilterAppliedAt(): Date | null;
|
||||
|
|
@ -65,7 +225,7 @@ export class MediaEngineStore extends FluxStore {
|
|||
getAudioSubsystem(): string;
|
||||
getAutomaticGainControl(): boolean;
|
||||
getBypassSystemInputProcessing(): boolean;
|
||||
getCameraComponent(): (e: any) => any;
|
||||
getCameraComponent(): React.ComponentType;
|
||||
getDebugLogging(): boolean;
|
||||
getEchoCancellation(): boolean;
|
||||
getEnableSilenceWarning(): boolean;
|
||||
|
|
@ -78,89 +238,99 @@ export class MediaEngineStore extends FluxStore {
|
|||
getHardwareEncoding(): boolean;
|
||||
getInputDetected(): boolean | null;
|
||||
getInputDeviceId(): string;
|
||||
getInputDevices(): Record<string, VideoDevice>;
|
||||
getInputDevices(): Record<string, AudioDevice>;
|
||||
getInputVolume(): number;
|
||||
getKrispEnableStats(): boolean;
|
||||
getKrispModelOverride(): string;
|
||||
getKrispModelOverride(): string | undefined;
|
||||
getKrispModels(): string[];
|
||||
getKrispSuppressionLevel(): number;
|
||||
getKrispVadActivationThreshold(): number;
|
||||
getLastAudioInputDeviceChangeTimestamp(): number;
|
||||
getLocalPan(key: string): Record<string, number>;
|
||||
getLocalVolume(): number;
|
||||
getLocalPan(userId: string, context?: string): LocalPan;
|
||||
getLocalVolume(userId: string, context?: string): number;
|
||||
getLoopback(): boolean;
|
||||
getLoopbackReasons(): Set<string>;
|
||||
getMediaEngine(): MediaEngine;
|
||||
getMode(): string;
|
||||
getModeOptions(): Record<string, number | boolean>;
|
||||
getMLSSigningKey(userId: string, guildId: string): Promise<unknown>;
|
||||
getMode(context?: string): string;
|
||||
getModeOptions(context?: string): ModeOptions;
|
||||
getMostRecentlyRequestedVoiceFilter(): string | null;
|
||||
getNoInputDetectedNotice(): boolean;
|
||||
getNoiseCancellation(): boolean;
|
||||
getNoiseSuppression(): boolean;
|
||||
getOutputDeviceId(): string;
|
||||
getOutputDevices(): Record<string, VideoDevice>;
|
||||
getOutputDevices(): Record<string, AudioDevice>;
|
||||
getOutputVolume(): number;
|
||||
getPacketDelay(): number;
|
||||
getPreviousVoiceFilter(): string | null;
|
||||
getPreviousVoiceFilterAppliedAt(): Date | null;
|
||||
getQoS(): boolean;
|
||||
getSettings(): Record<string, any>;
|
||||
getShortcuts(): Record<string, any>;
|
||||
getSettings(context?: string): Record<string, unknown>;
|
||||
getShortcuts(): Record<string, unknown>;
|
||||
getSidechainCompression(): boolean;
|
||||
getSidechainCompressionStrength(): number;
|
||||
getSpeakingWhileMuted(): boolean;
|
||||
getSupportedSecureFramesProtocolVersion(): boolean;
|
||||
getSystemMicrophoneMode(): boolean;
|
||||
getState(): {
|
||||
settingsByContext: Record<string, unknown>;
|
||||
inputDevices: Record<string, AudioDevice>;
|
||||
outputDevices: Record<string, AudioDevice>;
|
||||
appSupported: Record<string, boolean>;
|
||||
krispModuleLoaded: boolean;
|
||||
krispVersion: string | undefined;
|
||||
krispSuppressionLevel: number | undefined;
|
||||
goLiveSource: GoLiveSource | undefined;
|
||||
goLiveContext: string;
|
||||
};
|
||||
getSupportedSecureFramesProtocolVersion(): number;
|
||||
getSystemMicrophoneMode(): string | undefined;
|
||||
getUseGamescopeCapture(): boolean;
|
||||
getUseSystemScreensharePicker(): boolean;
|
||||
getUseVaapiEncoder(): boolean;
|
||||
getVideoComponent(): (e: any) => any;
|
||||
getVideoComponent(): React.ComponentType;
|
||||
getVideoDeviceId(): string;
|
||||
getVideoDevices(): Record<string, VideoDevice>;
|
||||
getVideoHook(): boolean;
|
||||
getVideoStreamParameters(): Record<string, string | number>;
|
||||
getVideoToggleState(key: string): string;
|
||||
getVideoStreamParameters(context?: string): VideoStreamParameter[];
|
||||
getVideoToggleState(userId: string, context?: string): string;
|
||||
getVoiceFilterPlaybackEnabled(): boolean;
|
||||
|
||||
goLiveSimulcastEnabled(): boolean;
|
||||
|
||||
hasActiveCallKitCall(): boolean;
|
||||
hasClipsSource(): boolean;
|
||||
hasContext(key: string): boolean;
|
||||
hasContext(context: string): boolean;
|
||||
hasH265HardwareDecode(): boolean;
|
||||
|
||||
isAdvancedVoiceActivitySupported(): boolean;
|
||||
isAecDumpSupported(): boolean;
|
||||
isAnyLocalVideoAutoDisabled(): boolean;
|
||||
isAnyLocalVideoAutoDisabled(context?: string): boolean;
|
||||
isAutomaticGainControlSupported(): boolean;
|
||||
isDeaf(): boolean;
|
||||
isEnableHardwareMuteNotice(): boolean;
|
||||
isEnabled(): boolean;
|
||||
isHardwareMute(): boolean;
|
||||
isHardwareMute(context?: string): boolean;
|
||||
isInputProfileCustom(): boolean;
|
||||
isInteractionRequired(): boolean;
|
||||
isLocalMute(): boolean;
|
||||
isLocalMute(userId: string): boolean;
|
||||
isLocalVideoAutoDisabled(userId: string): boolean;
|
||||
isLocalVideoDisabled(userId: string): boolean;
|
||||
isLocalVideoDisabled(): boolean;
|
||||
isLocalMute(userId: string, context?: string): boolean;
|
||||
isLocalVideoAutoDisabled(userId: string, context?: string): boolean;
|
||||
isLocalVideoDisabled(userId: string, context?: string): boolean;
|
||||
isMediaFilterSettingLoading(): boolean;
|
||||
isMute(): boolean;
|
||||
isNativeAudioPermissionReady(): boolean;
|
||||
isNoiseCancellationError(): boolean;
|
||||
isNoiseCancellationSupported(): boolean;
|
||||
isNoiseSuppressionSupported(): boolean;
|
||||
isScreenSharing(): boolean;
|
||||
isSelfDeaf(): boolean;
|
||||
isSelfMute(): boolean;
|
||||
isSelfMutedTemporarily(): boolean;
|
||||
isScreenSharing(context?: string): boolean;
|
||||
isSelfDeaf(context?: string): boolean;
|
||||
isSelfMute(context?: string): boolean;
|
||||
isSelfMutedTemporarily(context?: string): boolean;
|
||||
isSimulcastSupported(): boolean;
|
||||
isSoundSharing(): boolean;
|
||||
isSoundSharing(context?: string): boolean;
|
||||
isSupported(): boolean;
|
||||
isVideoAvailable(): boolean;
|
||||
isVideoEnabled(): boolean;
|
||||
|
||||
notifyMuteUnmuteSoundWasSkipped(): boolean;
|
||||
notifyMuteUnmuteSoundWasSkipped(): void;
|
||||
setCanHavePriority(userId: string, value: boolean): void;
|
||||
setHasActiveCallKitCall(active: boolean): void;
|
||||
shouldOfferManualSubsystemSelection(): boolean;
|
||||
|
|
@ -169,7 +339,7 @@ export class MediaEngineStore extends FluxStore {
|
|||
|
||||
startDavePreload(): void;
|
||||
|
||||
supports(e: string): boolean;
|
||||
supports(feature: string): boolean;
|
||||
supportsDisableLocalVideo(): boolean;
|
||||
supportsExperimentalSoundshare(): boolean;
|
||||
supportsHookSoundshare(): boolean;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ type State = {
|
|||
|
||||
export class OverridePremiumTypeStore extends FluxStore {
|
||||
getState(): State;
|
||||
getCreatedAtOverride(): Date;
|
||||
getCreatedAtOverride(): Date | undefined;
|
||||
getPremiumTypeActual(): number;
|
||||
getPremiumTypeOverride(): number;
|
||||
getPremiumTypeOverride(): number | undefined;
|
||||
get premiumType(): number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,16 +11,22 @@ import { SettingsTab, wrapTab } from "@components/settings";
|
|||
import { Margins } from "@utils/margins";
|
||||
import { TabBar, useState } from "@webpack/common";
|
||||
|
||||
import AccordionTab from "./tabs/AccordionTab";
|
||||
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 CardTab from "./tabs/CardTab";
|
||||
import CheckboxGroupTab from "./tabs/CheckboxGroupTab";
|
||||
import CheckboxTab from "./tabs/CheckboxTab";
|
||||
import ChipTab from "./tabs/ChipTab";
|
||||
import ClickableTab from "./tabs/ClickableTab";
|
||||
import ColorPickerTab from "./tabs/ColorPickerTab";
|
||||
import ComboboxTab from "./tabs/ComboboxTab";
|
||||
import DividerTab from "./tabs/DividerTab";
|
||||
import GuildIconTab from "./tabs/GuildIconTab";
|
||||
import HeadingTab from "./tabs/HeadingTab";
|
||||
import LayerModalTab from "./tabs/LayerModalTab";
|
||||
import ListboxTab from "./tabs/ListboxTab";
|
||||
import ModalTab from "./tabs/ModalTab";
|
||||
|
|
@ -30,8 +36,10 @@ import PopoverTab from "./tabs/PopoverTab";
|
|||
import ProgressBarTab from "./tabs/ProgressBarTab";
|
||||
import RadioGroupTab from "./tabs/RadioGroupTab";
|
||||
import RichTooltipTab from "./tabs/RichTooltipTab";
|
||||
import ScrollerTab from "./tabs/ScrollerTab";
|
||||
import SearchBarTab from "./tabs/SearchBarTab";
|
||||
import SelectTab from "./tabs/SelectTab";
|
||||
import SkeletonTab from "./tabs/SkeletonTab";
|
||||
import SliderTab from "./tabs/SliderTab";
|
||||
import SpinnerTab from "./tabs/SpinnerTab";
|
||||
import SwitchTab from "./tabs/SwitchTab";
|
||||
|
|
@ -39,20 +47,29 @@ import TabBarTab from "./tabs/TabBarTab";
|
|||
import TextAreaTab from "./tabs/TextAreaTab";
|
||||
import TextButtonTab from "./tabs/TextButtonTab";
|
||||
import TextInputTab from "./tabs/TextInputTab";
|
||||
import TimestampTab from "./tabs/TimestampTab";
|
||||
import ToastTab from "./tabs/ToastTab";
|
||||
import TooltipTab from "./tabs/TooltipTab";
|
||||
import TypographyTab from "./tabs/TypographyTab";
|
||||
|
||||
|
||||
const TABS = [
|
||||
{ id: "accordion", label: "Accordion" },
|
||||
{ id: "anchor", label: "Anchor" },
|
||||
{ id: "avatar", label: "Avatar" },
|
||||
{ id: "badge", label: "Badge" },
|
||||
{ id: "buttons", label: "Buttons" },
|
||||
{ id: "calendar", label: "Calendar" },
|
||||
{ id: "card", label: "Card" },
|
||||
{ id: "chip", label: "Chip" },
|
||||
{ id: "checkbox", label: "Checkbox" },
|
||||
{ id: "checkboxgroup", label: "CheckboxGroup" },
|
||||
{ id: "clickable", label: "Clickable" },
|
||||
{ id: "colorpicker", label: "ColorPicker" },
|
||||
{ id: "combobox", label: "Combobox" },
|
||||
{ id: "divider", label: "Divider" },
|
||||
{ id: "guildicon", label: "GuildIcon" },
|
||||
{ id: "heading", label: "Heading" },
|
||||
{ id: "layermodal", label: "LayerModal" },
|
||||
{ id: "listbox", label: "Listbox" },
|
||||
{ id: "modal", label: "Modal" },
|
||||
|
|
@ -62,8 +79,10 @@ const TABS = [
|
|||
{ id: "progressbar", label: "ProgressBar" },
|
||||
{ id: "radiogroup", label: "RadioGroup" },
|
||||
{ id: "richtooltip", label: "RichTooltip" },
|
||||
{ id: "scroller", label: "Scroller" },
|
||||
{ id: "searchbar", label: "SearchBar" },
|
||||
{ id: "select", label: "Select" },
|
||||
{ id: "skeleton", label: "Skeleton" },
|
||||
{ id: "slider", label: "Slider" },
|
||||
{ id: "spinner", label: "Spinner" },
|
||||
{ id: "switch", label: "Switch" },
|
||||
|
|
@ -71,23 +90,32 @@ const TABS = [
|
|||
{ id: "textarea", label: "TextArea" },
|
||||
{ id: "textbutton", label: "TextButton" },
|
||||
{ id: "textinput", label: "TextInput" },
|
||||
{ id: "timestamp", label: "Timestamp" },
|
||||
{ id: "toast", label: "Toast" },
|
||||
{ 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> = {
|
||||
accordion: AccordionTab,
|
||||
anchor: AnchorTab,
|
||||
avatar: AvatarTab,
|
||||
badge: BadgeTab,
|
||||
buttons: ButtonsTab,
|
||||
calendar: CalendarTab,
|
||||
card: CardTab,
|
||||
chip: ChipTab,
|
||||
checkbox: CheckboxTab,
|
||||
checkboxgroup: CheckboxGroupTab,
|
||||
clickable: ClickableTab,
|
||||
colorpicker: ColorPickerTab,
|
||||
combobox: ComboboxTab,
|
||||
divider: DividerTab,
|
||||
guildicon: GuildIconTab,
|
||||
heading: HeadingTab,
|
||||
layermodal: LayerModalTab,
|
||||
listbox: ListboxTab,
|
||||
modal: ModalTab,
|
||||
|
|
@ -97,8 +125,10 @@ const TAB_COMPONENTS: Record<TabId, React.ComponentType> = {
|
|||
progressbar: ProgressBarTab,
|
||||
radiogroup: RadioGroupTab,
|
||||
richtooltip: RichTooltipTab,
|
||||
scroller: ScrollerTab,
|
||||
searchbar: SearchBarTab,
|
||||
select: SelectTab,
|
||||
skeleton: SkeletonTab,
|
||||
slider: SliderTab,
|
||||
spinner: SpinnerTab,
|
||||
switch: SwitchTab,
|
||||
|
|
@ -106,6 +136,8 @@ const TAB_COMPONENTS: Record<TabId, React.ComponentType> = {
|
|||
textarea: TextAreaTab,
|
||||
textbutton: TextButtonTab,
|
||||
textinput: TextInputTab,
|
||||
timestamp: TimestampTab,
|
||||
toast: ToastTab,
|
||||
tooltip: TooltipTab,
|
||||
typography: TypographyTab,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,22 +4,63 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Button } from "@components/Button";
|
||||
import { Card } from "@components/Card";
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { findByCodeLazy, findComponentByCodeLazy, findLazy } from "@webpack";
|
||||
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findLazy } from "@webpack";
|
||||
import {
|
||||
GuildStore,
|
||||
ScrollerAuto,
|
||||
ScrollerNone,
|
||||
ScrollerThin,
|
||||
TabBar,
|
||||
useEffect,
|
||||
useRef,
|
||||
UserStore,
|
||||
useState,
|
||||
} from "@webpack/common";
|
||||
|
||||
export {
|
||||
Button,
|
||||
Card,
|
||||
GuildStore,
|
||||
Heading,
|
||||
Paragraph,
|
||||
ScrollerAuto,
|
||||
ScrollerNone,
|
||||
ScrollerThin,
|
||||
TabBar,
|
||||
useEffect,
|
||||
useRef,
|
||||
UserStore,
|
||||
useState,
|
||||
};
|
||||
|
||||
export * from "../constants";
|
||||
|
||||
import {
|
||||
AccordionProps,
|
||||
AnchorProps,
|
||||
AvatarProps,
|
||||
BadgeShapesType,
|
||||
CheckboxGroupOption,
|
||||
ChipProps,
|
||||
CircleBadgeProps,
|
||||
ClickableProps,
|
||||
ColorPickerProps,
|
||||
ColorPickerWithSwatchesProps,
|
||||
ColorSwatchProps,
|
||||
ConfirmModalProps,
|
||||
CustomColorButtonProps,
|
||||
DefaultColorButtonProps,
|
||||
DiscordHeadingProps,
|
||||
DiscordTextProps,
|
||||
ExpressiveModalProps,
|
||||
GradientColor,
|
||||
GuildIconProps,
|
||||
HeadingVariant,
|
||||
IconBadgeProps,
|
||||
ListboxItem,
|
||||
ManaBaseRadioGroupProps,
|
||||
|
|
@ -39,15 +80,22 @@ import {
|
|||
ManaTextButtonProps,
|
||||
ManaTextInputProps,
|
||||
ManaTooltipProps,
|
||||
ModalAction,
|
||||
ModalNotice,
|
||||
ModalProps,
|
||||
ModalRenderFn,
|
||||
NoticeProps,
|
||||
NoticeType,
|
||||
NumberBadgeProps,
|
||||
OpenModalOptions,
|
||||
PaginatorProps,
|
||||
PopoverAction,
|
||||
ProgressBarProps,
|
||||
RadioOption,
|
||||
SearchBarProps,
|
||||
SkeletonProps,
|
||||
SliderProps,
|
||||
SpinnerComponent,
|
||||
SpinnerProps,
|
||||
StandaloneRadioIndicatorProps,
|
||||
TabBarComponent,
|
||||
|
|
@ -55,21 +103,37 @@ import {
|
|||
TabBarItemProps,
|
||||
TabBarProps,
|
||||
TabBarSeparatorProps,
|
||||
TextBadgeProps
|
||||
TextBadgeProps,
|
||||
TextColor,
|
||||
TextVariant,
|
||||
TimestampProps,
|
||||
ToastData,
|
||||
ToastOptions,
|
||||
ToastPositionValue,
|
||||
ToastsModule,
|
||||
ToastTypeValue,
|
||||
} from "../types";
|
||||
|
||||
export type {
|
||||
AccordionProps,
|
||||
AnchorProps,
|
||||
AvatarProps,
|
||||
BadgeShapesType,
|
||||
CheckboxGroupOption,
|
||||
ChipProps,
|
||||
CircleBadgeProps,
|
||||
ColorPickerProps,
|
||||
ColorPickerWithSwatchesProps,
|
||||
ColorSwatchProps,
|
||||
ConfirmModalProps,
|
||||
CustomColorButtonProps,
|
||||
DefaultColorButtonProps,
|
||||
DiscordHeadingProps,
|
||||
DiscordTextProps,
|
||||
ExpressiveModalProps,
|
||||
GradientColor,
|
||||
GuildIconProps,
|
||||
HeadingVariant,
|
||||
IconBadgeProps,
|
||||
ListboxItem,
|
||||
ManaBaseRadioGroupProps,
|
||||
|
|
@ -89,14 +153,20 @@ export type {
|
|||
ManaTextButtonProps,
|
||||
ManaTextInputProps,
|
||||
ManaTooltipProps,
|
||||
ModalAction,
|
||||
ModalNotice,
|
||||
ModalProps,
|
||||
ModalRenderFn,
|
||||
NoticeProps,
|
||||
NoticeType,
|
||||
NumberBadgeProps,
|
||||
OpenModalOptions,
|
||||
PaginatorProps,
|
||||
PopoverAction,
|
||||
ProgressBarProps,
|
||||
RadioOption,
|
||||
SearchBarProps,
|
||||
SkeletonProps,
|
||||
SliderProps,
|
||||
SpinnerProps,
|
||||
StandaloneRadioIndicatorProps,
|
||||
|
|
@ -105,9 +175,34 @@ export type {
|
|||
TabBarItemProps,
|
||||
TabBarProps,
|
||||
TabBarSeparatorProps,
|
||||
TextBadgeProps
|
||||
TextBadgeProps,
|
||||
TextColor,
|
||||
TextVariant,
|
||||
TimestampProps,
|
||||
ToastData,
|
||||
ToastOptions,
|
||||
ToastPositionValue,
|
||||
ToastsModule,
|
||||
ToastTypeValue,
|
||||
};
|
||||
|
||||
export const ToastType = {
|
||||
MESSAGE: "message",
|
||||
SUCCESS: "success",
|
||||
FAILURE: "failure",
|
||||
CUSTOM: "custom",
|
||||
CLIP: "clip",
|
||||
LINK: "link",
|
||||
FORWARD: "forward",
|
||||
BOOKMARK: "bookmark",
|
||||
CLOCK: "clock",
|
||||
} as const;
|
||||
|
||||
export const ToastPosition = {
|
||||
TOP: 0,
|
||||
BOTTOM: 1,
|
||||
} as const;
|
||||
|
||||
export const Anchor = findComponentByCodeLazy("anchorUnderlineOnHover", "useDefaultUnderlineStyles") as React.ComponentType<AnchorProps>;
|
||||
|
||||
export const ManaButton = findComponentByCodeLazy('"data-mana-component":"button"') as React.ComponentType<ManaButtonProps>;
|
||||
|
|
@ -117,7 +212,7 @@ export const ManaTextInput = findComponentByCodeLazy('"data-mana-component":"tex
|
|||
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 ManaTooltip = findComponentByCodeLazy("VoidTooltip cannot find DOM node") 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>;
|
||||
|
|
@ -131,7 +226,7 @@ export const ManaPopover = findComponentByCodeLazy('"data-mana-component":"popov
|
|||
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 Spinner = findComponentByCodeLazy("spinningCircleSimple", "pulsingEllipsis", "wanderingCubes") as unknown as SpinnerComponent;
|
||||
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>;
|
||||
|
|
@ -184,6 +279,52 @@ export const IconBadge: React.ComponentType<IconBadgeProps> = proxyLazy(() => ge
|
|||
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>;
|
||||
|
||||
export const Chip = findComponentByCodeLazy('variant:"eyebrow"', "chip,") as React.ComponentType<ChipProps>;
|
||||
|
||||
export const Skeleton = findComponentByCodeLazy("productSkeletonCardContainer", "skipPulseAnimation") as React.ComponentType<SkeletonProps>;
|
||||
|
||||
export const Accordion = findComponentByCodeLazy("accordionContainer", "onExpandedChange", "defaultExpanded") as React.ComponentType<AccordionProps>;
|
||||
|
||||
export const Timestamp = findComponentByCodeLazy("isVisibleOnlyOnHover", "cozyAlt", "timestampFormat") as React.ComponentType<TimestampProps>;
|
||||
|
||||
export const GuildIcon = findComponentByCodeLazy("getGuildIconURL", "acronym") as React.ComponentType<GuildIconProps>;
|
||||
|
||||
const ToastsModule = findByPropsLazy("showToast", "popToast");
|
||||
export const Toasts: ToastsModule = proxyLazy(() => ({
|
||||
Type: ToastType,
|
||||
Position: ToastPosition,
|
||||
genId: () => Math.random().toString(36).slice(2),
|
||||
show: (toast: ToastData) => ToastsModule.showToast(toast),
|
||||
pop: () => ToastsModule.popToast(),
|
||||
create: (message: string, type: ToastTypeValue, options?: ToastOptions) => ToastsModule.createToast(message, type, options),
|
||||
}));
|
||||
export const showToast = (message: string, type: ToastTypeValue = ToastType.MESSAGE) => ToastsModule.showToast(ToastsModule.createToast(message, type));
|
||||
|
||||
const ModalModule = findByPropsLazy("Modal", "ConfirmModal");
|
||||
const ModalAPIModule = findByPropsLazy("openModal", "closeModal");
|
||||
|
||||
export const Modal: React.ComponentType<ModalProps> = proxyLazy(() => ModalModule.Modal);
|
||||
export const ConfirmModal: React.ComponentType<ConfirmModalProps> = proxyLazy(() => ModalModule.ConfirmModal);
|
||||
export const ExpressiveModal: React.ComponentType<ExpressiveModalProps> = proxyLazy(() => ModalModule.ExpressiveModal);
|
||||
|
||||
export const openModal = (render: ModalRenderFn, options?: OpenModalOptions): string => ModalAPIModule.openModal(render, options);
|
||||
export const closeModal = (modalKey: string): void => ModalAPIModule.closeModal(modalKey);
|
||||
export const closeAllModals = (): void => ModalAPIModule.closeAllModals();
|
||||
export const hasModalOpen = (modalKey: string): boolean => ModalAPIModule.hasModalOpen(modalKey);
|
||||
export const hasAnyModalOpen = (): boolean => ModalAPIModule.hasAnyModalOpen();
|
||||
|
||||
const DiscordTextModule = findByPropsLazy("Heading", "Text");
|
||||
export function DiscordHeading(props: DiscordHeadingProps) {
|
||||
const Component = DiscordTextModule.Heading;
|
||||
return <Component {...props} />;
|
||||
}
|
||||
export function DiscordText(props: DiscordTextProps) {
|
||||
const Component = DiscordTextModule.Text;
|
||||
return <Component {...props} />;
|
||||
}
|
||||
|
||||
export function Divider({ className, style, ...restProps }: React.ComponentPropsWithoutRef<"hr">) {
|
||||
return <hr className={`vc-divider${className ? ` ${className}` : ""}`} style={style} {...restProps} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Accordion, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function AccordionTab() {
|
||||
const [controlled, setControlled] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Basic Accordion">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Click to expand/collapse content.
|
||||
</Paragraph>
|
||||
<Accordion title="Click to expand">
|
||||
<Paragraph>This is the accordion content that is revealed when expanded.</Paragraph>
|
||||
</Accordion>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Subtitle">
|
||||
<Accordion title="Settings" subtitle="Configure options">
|
||||
<Paragraph>Content with a subtitle in the header.</Paragraph>
|
||||
</Accordion>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Default Expanded">
|
||||
<Accordion title="Already Open" defaultExpanded>
|
||||
<Paragraph>This accordion starts expanded by default.</Paragraph>
|
||||
</Accordion>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Controlled">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Use isExpanded and onExpandedChange for controlled state.
|
||||
</Paragraph>
|
||||
<Accordion
|
||||
title="Controlled Accordion"
|
||||
isExpanded={controlled}
|
||||
onExpandedChange={setControlled}
|
||||
>
|
||||
<Paragraph>This accordion's state is controlled externally.</Paragraph>
|
||||
</Accordion>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="With Max Height">
|
||||
<Accordion title="Limited Height" maxHeight={100}>
|
||||
<Paragraph>Line 1</Paragraph>
|
||||
<Paragraph>Line 2</Paragraph>
|
||||
<Paragraph>Line 3</Paragraph>
|
||||
<Paragraph>Line 4</Paragraph>
|
||||
<Paragraph>Line 5</Paragraph>
|
||||
<Paragraph>Line 6</Paragraph>
|
||||
</Accordion>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• title: string - Header title</Paragraph>
|
||||
<Paragraph color="text-muted">• subtitle?: string - Optional subtitle</Paragraph>
|
||||
<Paragraph color="text-muted">• icon?: ReactNode - Optional icon</Paragraph>
|
||||
<Paragraph color="text-muted">• children: ReactNode - Content</Paragraph>
|
||||
<Paragraph color="text-muted">• defaultExpanded?: boolean - Initial state (default: false)</Paragraph>
|
||||
<Paragraph color="text-muted">• isExpanded?: boolean - Controlled expanded state</Paragraph>
|
||||
<Paragraph color="text-muted">• onExpandedChange?: (expanded: boolean) => void</Paragraph>
|
||||
<Paragraph color="text-muted">• onOpen?: () => void - Called when opened</Paragraph>
|
||||
<Paragraph color="text-muted">• maxHeight?: number | string - Max content height</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,9 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { Anchor } from "..";
|
||||
import { Anchor , Paragraph } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function AnchorTab() {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,9 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { Avatar } from "..";
|
||||
import { Avatar, AvatarSizes, Paragraph, UserStore } 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);
|
||||
|
|
@ -23,7 +18,7 @@ export default function AvatarTab() {
|
|||
Avatar component from @webpack/common with various sizes.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid" style={{ alignItems: "flex-end" }}>
|
||||
{SIZES.map(size => (
|
||||
{AvatarSizes.map(size => (
|
||||
<div key={size} style={{ textAlign: "center" }}>
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
|
|
@ -109,7 +104,7 @@ export default function AvatarTab() {
|
|||
|
||||
<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">• size - SIZE_16, SIZE_20, SIZE_24, SIZE_32, SIZE_40, SIZE_44, SIZE_48, SIZE_56, SIZE_72, SIZE_80, SIZE_96, SIZE_120, SIZE_152</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>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { BadgeShapes, CircleBadge, IconBadge, NumberBadge, TextBadge } from "..";
|
||||
import { BadgeShapes, CircleBadge, IconBadge, NumberBadge, Paragraph,TextBadge } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,9 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton } from "..";
|
||||
import { ManaButton, ManaButtonSizes, ManaButtonVariants, Paragraph, useState } 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);
|
||||
|
||||
|
|
@ -20,7 +14,7 @@ export default function ButtonsTab() {
|
|||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Variants (size: md)">
|
||||
<div className="vc-compfinder-grid">
|
||||
{VARIANTS.map(variant => (
|
||||
{ManaButtonVariants.map(variant => (
|
||||
<ManaButton key={variant} variant={variant} size="md" text={variant} />
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -28,7 +22,7 @@ export default function ButtonsTab() {
|
|||
|
||||
<SectionWrapper title="Sizes (variant: primary)">
|
||||
<div className="vc-compfinder-grid">
|
||||
{SIZES.map(size => (
|
||||
{ManaButtonSizes.map(size => (
|
||||
<ManaButton key={size} variant="primary" size={size} text={`Size: ${size}`} />
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -54,7 +48,7 @@ export default function ButtonsTab() {
|
|||
|
||||
<SectionWrapper title="All Variants Disabled">
|
||||
<div className="vc-compfinder-grid">
|
||||
{VARIANTS.map(variant => (
|
||||
{ManaButtonVariants.map(variant => (
|
||||
<ManaButton key={variant} variant={variant} size="md" text={variant} disabled />
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaCalendar, ManaDatePicker } from "..";
|
||||
import { ManaCalendar, ManaDatePicker, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function CalendarTab() {
|
||||
|
|
|
|||
105
src/equicordplugins/components.dev/components/tabs/CardTab.tsx
Normal file
105
src/equicordplugins/components.dev/components/tabs/CardTab.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Card, CardVariants, Paragraph } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function CardTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Card Variants">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Card component from @components/Card with different severity styles.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
{CardVariants.map(variant => (
|
||||
<Card key={variant} variant={variant}>
|
||||
<Paragraph style={{ fontWeight: 600, marginBottom: 4 }}>
|
||||
{variant.charAt(0).toUpperCase() + variant.slice(1)} Card
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
This is a {variant} card variant. Use for {
|
||||
variant === "normal" ? "general content" :
|
||||
variant === "warning" ? "cautionary messages" :
|
||||
"error or destructive actions"
|
||||
}.
|
||||
</Paragraph>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Default Padding">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Cards have 1em padding by default when no className is passed.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginBottom: 4 }}>
|
||||
defaultPadding=true (or no className)
|
||||
</Paragraph>
|
||||
<Card defaultPadding>
|
||||
<Paragraph>Card with default padding</Paragraph>
|
||||
</Card>
|
||||
</div>
|
||||
<div>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginBottom: 4 }}>
|
||||
defaultPadding=false
|
||||
</Paragraph>
|
||||
<Card defaultPadding={false}>
|
||||
<Paragraph style={{ padding: 8 }}>Card without default padding (content adds own)</Paragraph>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Nested Cards">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Cards can be nested for complex layouts.
|
||||
</Paragraph>
|
||||
<Card variant="normal">
|
||||
<Paragraph style={{ fontWeight: 600, marginBottom: 8 }}>Outer Card</Paragraph>
|
||||
<Card variant="warning" style={{ marginBottom: 8 }}>
|
||||
<Paragraph>Nested warning card</Paragraph>
|
||||
</Card>
|
||||
<Card variant="danger">
|
||||
<Paragraph>Nested danger card</Paragraph>
|
||||
</Card>
|
||||
</Card>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Custom Styling">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Cards accept custom className and style props.
|
||||
</Paragraph>
|
||||
<Card
|
||||
variant="normal"
|
||||
style={{
|
||||
borderLeft: "4px solid var(--text-brand)",
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
<Paragraph>Card with custom border accent</Paragraph>
|
||||
</Card>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• variant - "normal" | "warning" | "danger" (default: "normal")</Paragraph>
|
||||
<Paragraph color="text-muted">• defaultPadding - Add 1em padding (default: true if no className)</Paragraph>
|
||||
<Paragraph color="text-muted">• className - Custom CSS class</Paragraph>
|
||||
<Paragraph color="text-muted">• style - Inline styles</Paragraph>
|
||||
<Paragraph color="text-muted">• children - Card content</Paragraph>
|
||||
<Paragraph color="text-muted">• ...restProps - Any other div attributes</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Import">
|
||||
<Paragraph color="text-muted">
|
||||
{"import { Card } from \"@components/Card\";"}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { CheckboxGroupOption, ManaCheckboxGroup } from "..";
|
||||
import { CheckboxGroupOption, ManaCheckboxGroup, Paragraph, useState } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaCheckbox } from "..";
|
||||
import { ManaCheckbox, Paragraph, useState } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Chip , Paragraph } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function ChipTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Colors">
|
||||
<div className="vc-compfinder-grid">
|
||||
<Chip text="Blurple" variant="blurpleMedium" />
|
||||
<Chip text="Purple" variant="purpleMedium" />
|
||||
<Chip text="Green" variant="greenMedium" />
|
||||
<Chip text="Orange" variant="orangeMedium" />
|
||||
<Chip text="Yellow" variant="yellowMedium" />
|
||||
<Chip text="Pink" variant="pinkMedium" />
|
||||
<Chip text="Red" variant="redMedium" />
|
||||
<Chip text="Gray" variant="grayMedium" />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Shades">
|
||||
<div className="vc-compfinder-grid">
|
||||
<Chip text="Light" variant="blurpleLight" />
|
||||
<Chip text="Medium" variant="blurpleMedium" />
|
||||
<Chip text="Dark" variant="blurpleDark" />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Use Cases">
|
||||
<div className="vc-compfinder-grid">
|
||||
<Chip text="New" variant="blurpleLight" />
|
||||
<Chip text="Beta" variant="yellowMedium" />
|
||||
<Chip text="Deprecated" variant="redLight" />
|
||||
<Chip text="Stable" variant="greenLight" />
|
||||
<Chip text="Premium" variant="pinkMedium" />
|
||||
<Chip text="Experimental" variant="orangeLight" />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• text: string - Text to display inside the chip
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• variant?: ChipVariant - Color variant (default: "grayLight")
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { Clickable } from "..";
|
||||
import { Clickable, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function ClickableTab() {
|
||||
|
|
|
|||
|
|
@ -4,21 +4,11 @@
|
|||
* 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 { ColorPicker, ColorPickerWithSwatches, ColorSwatch, CustomColorButton, DefaultColorButton, Paragraph, PresetColors, useState } 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");
|
||||
}
|
||||
|
|
@ -84,7 +74,7 @@ export default function ColorPickerTab() {
|
|||
Individual color swatch buttons. Click to select.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
{PRESET_COLORS.slice(0, 10).map(color => (
|
||||
{PresetColors.slice(0, 10).map(color => (
|
||||
<ColorSwatch
|
||||
key={color}
|
||||
color={color}
|
||||
|
|
@ -102,27 +92,27 @@ export default function ColorPickerTab() {
|
|||
<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>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>Normal</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} isSelected />
|
||||
<Paragraph color="text-muted" size="xs">Selected</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>Selected</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} isDefault />
|
||||
<Paragraph color="text-muted" size="xs">Default</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>Default</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} isCustom />
|
||||
<Paragraph color="text-muted" size="xs">Custom</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>Custom</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch color={0x5865F2} disabled />
|
||||
<Paragraph color="text-muted" size="xs">Disabled</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>Disabled</Paragraph>
|
||||
</div>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ColorSwatch />
|
||||
<Paragraph color="text-muted" size="xs">No Color</Paragraph>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>No Color</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
|
@ -174,7 +164,7 @@ export default function ColorPickerTab() {
|
|||
<CustomColorButton
|
||||
customColor={customBtnColor}
|
||||
value={customBtnColor}
|
||||
presets={PRESET_COLORS}
|
||||
presets={[...PresetColors]}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
|
|
@ -184,7 +174,7 @@ export default function ColorPickerTab() {
|
|||
</Paragraph>
|
||||
<ColorPickerWithSwatches
|
||||
defaultColor={DEFAULT_COLOR}
|
||||
colors={PRESET_COLORS}
|
||||
colors={[...PresetColors]}
|
||||
value={swatchColor}
|
||||
onChange={setSwatchColor}
|
||||
renderDefaultButton={() => null}
|
||||
|
|
@ -199,7 +189,7 @@ export default function ColorPickerTab() {
|
|||
<ColorPickerWithSwatches
|
||||
defaultColor={DEFAULT_COLOR}
|
||||
customColor={customSwatchColor}
|
||||
colors={PRESET_COLORS}
|
||||
colors={[...PresetColors]}
|
||||
value={customSwatchColor}
|
||||
onChange={setCustomSwatchColor}
|
||||
renderDefaultButton={props => (
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton, ManaCombobox } from "..";
|
||||
import { ManaButton, ManaCombobox, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SAMPLE_ITEMS = [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Divider, Paragraph } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function DividerTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Vencord Divider">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Simple horizontal rule from @components/Divider.
|
||||
</Paragraph>
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<Paragraph>Content above divider</Paragraph>
|
||||
<Divider />
|
||||
<Paragraph>Content below divider</Paragraph>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Custom Styled Dividers">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Dividers with custom margins and styles.
|
||||
</Paragraph>
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<Paragraph>Tight spacing (8px)</Paragraph>
|
||||
<Divider style={{ margin: "8px 0" }} />
|
||||
<Paragraph>Medium spacing (16px)</Paragraph>
|
||||
<Divider style={{ margin: "16px 0" }} />
|
||||
<Paragraph>Large spacing (24px)</Paragraph>
|
||||
<Divider style={{ margin: "24px 0" }} />
|
||||
<Paragraph>End</Paragraph>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• className - Custom CSS class</Paragraph>
|
||||
<Paragraph color="text-muted">• style - Inline styles</Paragraph>
|
||||
<Paragraph color="text-muted">• ...restProps - Any other hr attributes</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Import">
|
||||
<Paragraph color="text-muted">
|
||||
{"import { Divider } from \"@components/Divider\";"}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { GuildIcon, GuildIconSizes, GuildStore, ManaButton, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function GuildIconTab() {
|
||||
const [animate, setAnimate] = useState(true);
|
||||
|
||||
const guilds = Object.values(GuildStore.getGuilds()).slice(0, 6);
|
||||
const guildWithIcon = guilds.find(g => g.icon);
|
||||
const guildWithoutIcon = guilds.find(g => !g.icon) ?? guilds[0];
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Controls">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={`Animate: ${animate}`}
|
||||
onClick={() => setAnimate(a => !a)}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Icon Sizes">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
GuildIcon component with various sizes. Shows server icon or acronym fallback.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid" style={{ alignItems: "flex-end" }}>
|
||||
{guildWithIcon && GuildIconSizes.map(size => (
|
||||
<div key={size} style={{ textAlign: "center" }}>
|
||||
<GuildIcon
|
||||
guildId={guildWithIcon.id}
|
||||
guildName={guildWithIcon.name}
|
||||
guildIcon={guildWithIcon.icon ?? null}
|
||||
iconSize={size}
|
||||
animate={animate}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
{size}px
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Acronym Fallback">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
When a guild has no icon, displays an acronym based on the guild name.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid" style={{ alignItems: "flex-end" }}>
|
||||
{[32, 48, 80].map(size => (
|
||||
<div key={size} style={{ textAlign: "center" }}>
|
||||
<GuildIcon
|
||||
guildId={guildWithoutIcon?.id ?? "0"}
|
||||
guildName={guildWithoutIcon?.name ?? "Test Guild"}
|
||||
guildIcon={null}
|
||||
iconSize={size}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
{size}px (no icon)
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Your Guilds">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Showing icons from guilds you're in.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{guilds.map(guild => (
|
||||
<div key={guild.id} style={{ textAlign: "center", maxWidth: 80 }}>
|
||||
<GuildIcon
|
||||
guildId={guild.id}
|
||||
guildName={guild.name}
|
||||
guildIcon={guild.icon ?? null}
|
||||
iconSize={48}
|
||||
animate={animate}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{guild.name.slice(0, 12)}
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• guildId - The guild's snowflake ID</Paragraph>
|
||||
<Paragraph color="text-muted">• guildName - Guild name (used for acronym fallback)</Paragraph>
|
||||
<Paragraph color="text-muted">• guildIcon - Icon hash or null</Paragraph>
|
||||
<Paragraph color="text-muted">• iconSize - Size in pixels</Paragraph>
|
||||
<Paragraph color="text-muted">• animate - Enable animated icons (GIF)</Paragraph>
|
||||
<Paragraph color="text-muted">• className - Custom CSS class</Paragraph>
|
||||
<Paragraph color="text-muted">• acronymClassName - Class for acronym text</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Import">
|
||||
<Paragraph color="text-muted">
|
||||
{"import { GuildIcon } from \"@equicordplugins/components.dev/components\";"}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { DiscordHeading, DiscordText, HeadingVariants, Paragraph,TextColors, TextVariants } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function HeadingTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Discord Heading">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Discord's Heading component with variant prop. Auto-sets h1-h6 tag based on nesting level.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
{HeadingVariants.slice(0, 10).map(variant => (
|
||||
<DiscordHeading key={variant} variant={variant}>
|
||||
{variant}
|
||||
</DiscordHeading>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="More Heading Sizes">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
{HeadingVariants.slice(10).map(variant => (
|
||||
<DiscordHeading key={variant} variant={variant}>
|
||||
{variant}
|
||||
</DiscordHeading>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Discord Text">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Discord's Text component for body text with various sizes and weights.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{TextVariants.slice(0, 12).map(variant => (
|
||||
<DiscordText key={variant} variant={variant}>
|
||||
{variant}
|
||||
</DiscordText>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Smaller Text Sizes">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{TextVariants.slice(12).map(variant => (
|
||||
<DiscordText key={variant} variant={variant}>
|
||||
{variant}
|
||||
</DiscordText>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Text Colors">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Available color props for Text component.
|
||||
</Paragraph>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{TextColors.map(color => (
|
||||
<DiscordText key={color} variant="text-md/medium" color={color}>
|
||||
{color}
|
||||
</DiscordText>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props - DiscordHeading">
|
||||
<Paragraph color="text-muted">• variant - "heading-SIZE/WEIGHT" (xxl, xl, lg, md, sm)</Paragraph>
|
||||
<Paragraph color="text-muted">• className - Custom CSS class</Paragraph>
|
||||
<Paragraph color="text-muted">• color - Text color (same as Text colors)</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props - DiscordText">
|
||||
<Paragraph color="text-muted">• variant - "text-SIZE/WEIGHT" (xxl, xl, lg, md, sm, xs)</Paragraph>
|
||||
<Paragraph color="text-muted">• color - Text color name</Paragraph>
|
||||
<Paragraph color="text-muted">• tag - HTML tag to render (default: "div")</Paragraph>
|
||||
<Paragraph color="text-muted">• selectable - Allow text selection</Paragraph>
|
||||
<Paragraph color="text-muted">• lineClamp - Number of lines before truncation</Paragraph>
|
||||
<Paragraph color="text-muted">• tabularNumbers - Use tabular number spacing</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Import">
|
||||
<Paragraph color="text-muted">
|
||||
{"import { DiscordHeading, DiscordText, HeadingVariants, TextVariants, TextColors } from \"../components\";"}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,13 +4,10 @@
|
|||
* 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 { Heading, ManaButton, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const LayerModal = findComponentByCodeLazy('"data-mana-component":"layer-modal"') as React.ComponentType<{
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ListboxItem, ManaListbox } from "..";
|
||||
import { ListboxItem, ManaListbox, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SAMPLE_ITEMS: ListboxItem[] = [
|
||||
|
|
|
|||
|
|
@ -4,156 +4,229 @@
|
|||
* 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 "..";
|
||||
closeAllModals,
|
||||
ConfirmModal,
|
||||
ExpressiveModal,
|
||||
ManaButton,
|
||||
ManaTextInput,
|
||||
Modal,
|
||||
openModal,
|
||||
Paragraph,
|
||||
showToast,
|
||||
ToastType,
|
||||
useState,
|
||||
} 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) {
|
||||
function openBasicModal() {
|
||||
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>
|
||||
<Modal
|
||||
title="Basic Modal"
|
||||
subtitle="This is a subtitle"
|
||||
transitionState={props.transitionState}
|
||||
onClose={props.onClose}
|
||||
actions={[
|
||||
{ text: "Cancel", variant: "secondary", onClick: props.onClose },
|
||||
{ text: "Confirm", variant: "primary", onClick: () => { showToast("Confirmed!", ToastType.SUCCESS); props.onClose(); } },
|
||||
]}
|
||||
>
|
||||
<Paragraph>This is the modal content. You can put any React components here.</Paragraph>
|
||||
</Modal>
|
||||
));
|
||||
}
|
||||
|
||||
function openModalWithNotice() {
|
||||
openModal(props => (
|
||||
<Modal
|
||||
title="Modal with Notice"
|
||||
transitionState={props.transitionState}
|
||||
onClose={props.onClose}
|
||||
notice={{ message: "This is an informational notice", type: "info" }}
|
||||
actions={[
|
||||
{ text: "Got it", variant: "primary", onClick: props.onClose },
|
||||
]}
|
||||
>
|
||||
<Paragraph>Modals can display notices at the top for important information.</Paragraph>
|
||||
</Modal>
|
||||
));
|
||||
}
|
||||
|
||||
function ModalWithInputContent({ onClose, transitionState }: { onClose: () => void; transitionState: number; }) {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Modal with Input"
|
||||
subtitle="Enter some information"
|
||||
transitionState={transitionState}
|
||||
onClose={onClose}
|
||||
actions={[
|
||||
{ text: "Cancel", variant: "secondary", onClick: onClose },
|
||||
{ text: "Submit", variant: "primary", onClick: () => { showToast(`Submitted: ${inputValue}`, ToastType.SUCCESS); onClose(); } },
|
||||
]}
|
||||
>
|
||||
<ManaTextInput
|
||||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
placeholder="Type something..."
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ marginTop: 8 }}>
|
||||
Using ManaTextInput inside the modal content.
|
||||
</Paragraph>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function openModalWithInput() {
|
||||
openModal(props => (
|
||||
<ModalWithInputContent onClose={props.onClose} transitionState={props.transitionState} />
|
||||
));
|
||||
}
|
||||
|
||||
function openConfirmModal() {
|
||||
openModal(props => (
|
||||
<ConfirmModal
|
||||
title="Confirm Action"
|
||||
subtitle="Are you sure you want to proceed?"
|
||||
transitionState={props.transitionState}
|
||||
confirmText="Delete"
|
||||
cancelText="Cancel"
|
||||
variant="critical"
|
||||
onConfirm={() => {
|
||||
showToast("Item deleted!", ToastType.SUCCESS);
|
||||
}}
|
||||
onCancel={() => {
|
||||
showToast("Cancelled", ToastType.MESSAGE);
|
||||
}}
|
||||
onClose={props.onClose}
|
||||
>
|
||||
<Paragraph>This action cannot be undone. The item will be permanently deleted.</Paragraph>
|
||||
</ConfirmModal>
|
||||
));
|
||||
}
|
||||
|
||||
function openConfirmModalPrimary() {
|
||||
openModal(props => (
|
||||
<ConfirmModal
|
||||
title="Save Changes"
|
||||
transitionState={props.transitionState}
|
||||
confirmText="Save"
|
||||
variant="primary"
|
||||
onConfirm={() => {
|
||||
showToast("Changes saved!", ToastType.SUCCESS);
|
||||
}}
|
||||
onClose={props.onClose}
|
||||
>
|
||||
<Paragraph>Do you want to save your changes?</Paragraph>
|
||||
</ConfirmModal>
|
||||
));
|
||||
}
|
||||
|
||||
function openExpressiveModal() {
|
||||
openModal(props => (
|
||||
<ExpressiveModal
|
||||
title="Welcome!"
|
||||
description="This is an expressive modal with a more visual design."
|
||||
transitionState={props.transitionState}
|
||||
actions={[
|
||||
{ text: "Get Started", variant: "primary", onClick: props.onClose },
|
||||
]}
|
||||
onClose={props.onClose}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
export default function ModalTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Modal Sizes">
|
||||
<SectionWrapper title="Basic Modal">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Uses ModalRoot, ModalHeader, ModalContent, ModalFooter from @utils/modal
|
||||
and native ModalCloseButton via findComponentByCodeLazy("CLOSE_BUTTON_LABEL")
|
||||
Simple modal with title, subtitle, content, and action buttons.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{SIZES.map(({ size, label }) => (
|
||||
<ManaButton
|
||||
key={size}
|
||||
variant="secondary"
|
||||
text={label}
|
||||
onClick={() => openDemoModal(size, label)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ManaButton variant="secondary" text="Open Basic Modal" onClick={openBasicModal} />
|
||||
</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 title="Modal with Notice">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Modal with a notice banner (info, warn, critical, positive).
|
||||
</Paragraph>
|
||||
<ManaButton variant="secondary" text="Open Modal with Notice" onClick={openModalWithNotice} />
|
||||
</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 title="Modal with Input">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Modal with input controls in the header area.
|
||||
</Paragraph>
|
||||
<ManaButton variant="secondary" text="Open Modal with Input" onClick={openModalWithInput} />
|
||||
</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 title="Confirm Modal (Critical)">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Confirmation dialog for destructive actions.
|
||||
</Paragraph>
|
||||
<ManaButton variant="critical-secondary" text="Open Confirm Modal" onClick={openConfirmModal} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<SectionWrapper title="Confirm Modal (Primary)">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Confirmation dialog for non-destructive actions.
|
||||
</Paragraph>
|
||||
<ManaButton variant="secondary" text="Open Save Dialog" onClick={openConfirmModalPrimary} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Expressive Modal">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Visual modal with graphic/badge support.
|
||||
</Paragraph>
|
||||
<ManaButton variant="secondary" text="Open Expressive Modal" onClick={openExpressiveModal} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Close All Modals">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Utility to close all open modals at once.
|
||||
</Paragraph>
|
||||
<ManaButton variant="critical-secondary" text="Close All Modals" onClick={closeAllModals} />
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Modal Props">
|
||||
<Paragraph color="text-muted">• title - Modal title (required)</Paragraph>
|
||||
<Paragraph color="text-muted">• subtitle - Optional subtitle</Paragraph>
|
||||
<Paragraph color="text-muted">• size - "sm" or "md"</Paragraph>
|
||||
<Paragraph color="text-muted">• children - Modal content</Paragraph>
|
||||
<Paragraph color="text-muted">• actions - Array of action buttons</Paragraph>
|
||||
<Paragraph color="text-muted">• notice - {"{ message, type }"} for notices</Paragraph>
|
||||
<Paragraph color="text-muted">• input - Controls in header area</Paragraph>
|
||||
<Paragraph color="text-muted">• onClose - Close handler (required)</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ConfirmModal Props">
|
||||
<Paragraph color="text-muted">• confirmText - Confirm button text</Paragraph>
|
||||
<Paragraph color="text-muted">• cancelText - Cancel button text</Paragraph>
|
||||
<Paragraph color="text-muted">• variant - "critical" | "primary" | "secondary"</Paragraph>
|
||||
<Paragraph color="text-muted">• onConfirm - Confirm callback (can be async)</Paragraph>
|
||||
<Paragraph color="text-muted">• onCancel - Cancel callback</Paragraph>
|
||||
<Paragraph color="text-muted">• checkboxProps - Optional checkbox</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Action Button">
|
||||
<Paragraph color="text-muted">• text - Button text</Paragraph>
|
||||
<Paragraph color="text-muted">• variant - "primary" | "secondary" | "critical-primary"</Paragraph>
|
||||
<Paragraph color="text-muted">• onClick - Click handler</Paragraph>
|
||||
<Paragraph color="text-muted">• disabled - Disable button</Paragraph>
|
||||
<Paragraph color="text-muted">• loading - Show loading state</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="openModal Options">
|
||||
<Paragraph color="text-muted">• modalKey - Unique key for the modal</Paragraph>
|
||||
<Paragraph color="text-muted">• dismissable - Allow clicking backdrop to close</Paragraph>
|
||||
<Paragraph color="text-muted">• instant - Skip animation</Paragraph>
|
||||
<Paragraph color="text-muted">• onCloseCallback - Called when modal closes</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Import">
|
||||
<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
|
||||
{"import { Modal, ConfirmModal, openModal, closeModal } from \"../components\";"}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,9 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Button } from "@components/Button";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { Notice, NoticeType } from "..";
|
||||
import { Button, Notice, NoticeTypes, Paragraph } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const NOTICE_TYPES: NoticeType[] = ["info", "warn", "danger", "positive", "preview"];
|
||||
|
||||
export default function NoticeTab() {
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
|
|
@ -20,7 +15,7 @@ export default function NoticeTab() {
|
|||
Notice component for displaying informational messages with different severity levels.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{NOTICE_TYPES.map(type => (
|
||||
{NoticeTypes.map(type => (
|
||||
<div key={type} style={{ textAlign: "center" }}>
|
||||
<Notice messageType={type}>
|
||||
This is a {type} notice message.
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { Paginator2 as Paginator } from "..";
|
||||
import { Paginator2 as Paginator, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function PaginatorTab() {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* 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 { ManaButton, ManaPopover, Paragraph, PopoverAction, useRef, useState } 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; }) {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { ProgressBar } from "..";
|
||||
import { Paragraph, ProgressBar, useEffect, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function ProgressBarTab() {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaBaseRadioGroup, RadioOption, StandaloneRadioIndicator } from "..";
|
||||
import { ManaBaseRadioGroup, Paragraph, RadioOption, StandaloneRadioIndicator, useState } from "..";
|
||||
import { EquicordIcon } from "../icons/EquicordIcon";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { ManaButton, ManaRichTooltip } from "..";
|
||||
import { ManaButton, ManaRichTooltip , Paragraph } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function RichTooltipTab() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ManaButton, Paragraph, ScrollerAuto, ScrollerNone, ScrollerThin, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SAMPLE_ITEMS = Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`);
|
||||
|
||||
export default function ScrollerTab() {
|
||||
const [orientation, setOrientation] = useState<"vertical" | "horizontal">("vertical");
|
||||
const [fade, setFade] = useState(true);
|
||||
|
||||
const scrollerStyle = orientation === "vertical"
|
||||
? { height: 200, width: "100%", border: "1px solid var(--background-modifier-accent)", borderRadius: 8 }
|
||||
: { height: 60, width: "100%", border: "1px solid var(--background-modifier-accent)", borderRadius: 8 };
|
||||
|
||||
const itemStyle = orientation === "vertical"
|
||||
? { padding: "8px 12px", borderBottom: "1px solid var(--background-modifier-accent)" }
|
||||
: { padding: "8px 16px", display: "inline-block", whiteSpace: "nowrap" as const };
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Controls">
|
||||
<div className="vc-compfinder-grid">
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={`Orientation: ${orientation}`}
|
||||
onClick={() => setOrientation(o => o === "vertical" ? "horizontal" : "vertical")}
|
||||
/>
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={`Fade: ${fade}`}
|
||||
onClick={() => setFade(f => !f)}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ScrollerThin">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Thin scrollbar, most commonly used. Ideal for sidebars and lists.
|
||||
</Paragraph>
|
||||
<ScrollerThin orientation={orientation} fade={fade} style={scrollerStyle}>
|
||||
{SAMPLE_ITEMS.map(item => (
|
||||
<div key={item} style={itemStyle}>
|
||||
<Paragraph>{item}</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</ScrollerThin>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ScrollerAuto">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Standard scrollbar that appears automatically when content overflows.
|
||||
</Paragraph>
|
||||
<ScrollerAuto orientation={orientation} fade={fade} style={scrollerStyle}>
|
||||
{SAMPLE_ITEMS.map(item => (
|
||||
<div key={item} style={itemStyle}>
|
||||
<Paragraph>{item}</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</ScrollerAuto>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ScrollerNone">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
No visible scrollbar, content still scrollable via mouse wheel/touch.
|
||||
</Paragraph>
|
||||
<ScrollerNone orientation={orientation} fade={fade} style={scrollerStyle}>
|
||||
{SAMPLE_ITEMS.map(item => (
|
||||
<div key={item} style={itemStyle}>
|
||||
<Paragraph>{item}</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</ScrollerNone>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">• orientation - "vertical" | "horizontal" (default: "vertical")</Paragraph>
|
||||
<Paragraph color="text-muted">• fade - boolean, adds fade effect at edges</Paragraph>
|
||||
<Paragraph color="text-muted">• className - custom CSS class</Paragraph>
|
||||
<Paragraph color="text-muted">• style - inline styles</Paragraph>
|
||||
<Paragraph color="text-muted">• children - content to scroll</Paragraph>
|
||||
<Paragraph color="text-muted">• dir - text direction ("ltr" | "rtl")</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Variants">
|
||||
<Paragraph color="text-muted">• ScrollerThin - Thin scrollbar (most common)</Paragraph>
|
||||
<Paragraph color="text-muted">• ScrollerAuto - Standard auto-showing scrollbar</Paragraph>
|
||||
<Paragraph color="text-muted">• ScrollerNone - Hidden scrollbar</Paragraph>
|
||||
<Paragraph color="text-muted">• ListScrollerThin - Virtualized list with thin scrollbar</Paragraph>
|
||||
<Paragraph color="text-muted">• ListScrollerAuto - Virtualized list with auto scrollbar</Paragraph>
|
||||
<Paragraph color="text-muted">• ListScrollerNone - Virtualized list with hidden scrollbar</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Import">
|
||||
<Paragraph color="text-muted">
|
||||
{"import { ScrollerThin, ScrollerAuto, ScrollerNone } from \"@webpack/common\";"}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { SearchBar } from "..";
|
||||
import { Paragraph, SearchBar, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function SearchBarTab() {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaSelect, ManaSelectOption } from "..";
|
||||
import { ManaSelect, ManaSelectOption, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const SAMPLE_OPTIONS: ManaSelectOption[] = [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ManaButton, Paragraph, Skeleton, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function SkeletonTab() {
|
||||
const [animated, setAnimated] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Default Skeleton">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Loading placeholder with pulse animation. Requires "full-motion" parent class for animation.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid full-motion">
|
||||
<Skeleton />
|
||||
<Skeleton />
|
||||
<Skeleton />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Animation Toggle">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Use skipPulseAnimation to disable the pulse effect.
|
||||
</Paragraph>
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={animated ? "Disable Animation" : "Enable Animation"}
|
||||
onClick={() => setAnimated(!animated)}
|
||||
/>
|
||||
<div className="vc-compfinder-grid full-motion" style={{ marginTop: 16 }}>
|
||||
<Skeleton skipPulseAnimation={!animated} />
|
||||
<Skeleton skipPulseAnimation={!animated} />
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• skipPulseAnimation?: boolean - Disable pulse animation (default: false)
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Notes">
|
||||
<Paragraph color="text-muted">
|
||||
• This is a product card skeleton used in Discord's shop.
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• Animation requires a parent with "full-motion" class (Discord's reduced motion setting).
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• The skeleton has fixed dimensions designed for product cards.
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { Slider } from "..";
|
||||
import { Paragraph, Slider, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function SliderTab() {
|
||||
|
|
|
|||
|
|
@ -4,21 +4,9 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaButton, Spinner } from "..";
|
||||
import { ManaButton, Paragraph, Spinner, SpinnerTypes, useState } 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);
|
||||
|
||||
|
|
@ -29,7 +17,7 @@ export default function SpinnerTab() {
|
|||
All available spinner variants.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{SPINNER_TYPES.map(type => (
|
||||
{SpinnerTypes.map(type => (
|
||||
<div key={type} style={{ textAlign: "center", padding: 16 }}>
|
||||
<Spinner type={type} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 8 }}>
|
||||
|
|
@ -75,7 +63,7 @@ export default function SpinnerTab() {
|
|||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• type?: "wanderingCubes" | "chasingDots" | "pulsingEllipsis" | "spinningCircle" | "spinningCircleSimple" | "lowMotion"
|
||||
• type?: SpinnerType - Spinner variant
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• animated?: boolean - Enable/disable animation (default: true)
|
||||
|
|
@ -90,6 +78,15 @@ export default function SpinnerTab() {
|
|||
• aria-label?: string - Accessibility label
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Type Values">
|
||||
<Paragraph color="text-muted">• "wanderingCubes"</Paragraph>
|
||||
<Paragraph color="text-muted">• "chasingDots"</Paragraph>
|
||||
<Paragraph color="text-muted">• "pulsingEllipsis"</Paragraph>
|
||||
<Paragraph color="text-muted">• "spinningCircle"</Paragraph>
|
||||
<Paragraph color="text-muted">• "spinningCircleSimple"</Paragraph>
|
||||
<Paragraph color="text-muted">• "lowMotion"</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@
|
|||
* 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 { ManaSwitch, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
interface SwitchWithLabelProps {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { TabBar } from "..";
|
||||
import { Paragraph, TabBar, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function TabBarTab() {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaTextArea } from "..";
|
||||
import { ManaTextArea, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function TextAreaTab() {
|
||||
|
|
|
|||
|
|
@ -4,28 +4,15 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { ManaTextButton } from "..";
|
||||
import { ManaTextButton, ManaTextButtonTextVariants, ManaTextButtonVariants, Paragraph } 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 => (
|
||||
{ManaTextButtonVariants.map(variant => (
|
||||
<ManaTextButton
|
||||
key={variant}
|
||||
text={variant}
|
||||
|
|
@ -37,7 +24,7 @@ export default function TextButtonTab() {
|
|||
|
||||
<SectionWrapper title="Text Variants">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{TEXT_VARIANTS.map(textVariant => (
|
||||
{ManaTextButtonTextVariants.map(textVariant => (
|
||||
<ManaTextButton
|
||||
key={textVariant}
|
||||
text={textVariant}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { useState } from "@webpack/common";
|
||||
|
||||
import { ManaTextInput } from "..";
|
||||
import { ManaTextInput, Paragraph, useState } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function TextInputTab() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph, Timestamp, TimestampDisplayFormats, TooltipPositions } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
export default function TimestampTab() {
|
||||
const now = new Date();
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Timestamp Formats">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
All available timestamp format options using moment.js format strings.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{TimestampDisplayFormats.map(({ format, description }) => (
|
||||
<div key={format} style={{ padding: 12, display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
<Timestamp timestamp={now} timestampFormat={format} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>
|
||||
{format}: {description}
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Compact Mode">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Compact mode displays a shorter version of the timestamp.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} compact={false} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
compact=false (default)
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} compact={true} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
compact=true
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Display Variants">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Different display modes for timestamps.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} isInline={false} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
isInline=false (default)
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} isInline={true} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
isInline=true
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} cozyAlt={true} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
cozyAlt=true
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Edited Indicator">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Shows an "(edited)" indicator after the timestamp.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} isEdited={false} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
isEdited=false (default)
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} isEdited={true} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
isEdited=true
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Visibility on Hover">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Timestamp can be hidden until hovered.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
<div style={{ padding: 12 }}>
|
||||
<span style={{ padding: 8, background: "var(--background-secondary)", borderRadius: 4 }}>
|
||||
Hover me: <Timestamp timestamp={now} isVisibleOnlyOnHover={true} />
|
||||
</span>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
isVisibleOnlyOnHover=true
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Tooltip Positions">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
The tooltip showing full date can be positioned differently.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid">
|
||||
{TooltipPositions.map(position => (
|
||||
<div key={position} style={{ padding: 12 }}>
|
||||
<Timestamp timestamp={now} tooltipPosition={position} />
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10, marginTop: 4 }}>
|
||||
tooltipPosition="{position}"
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Props">
|
||||
<Paragraph color="text-muted">
|
||||
• timestamp: Date | Moment - The timestamp to display (required)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• timestampFormat?: TimestampFormat - Format string (LT, LTS, L, LL, LLL, LLLL, l, ll, lll, llll)
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• compact?: boolean - Use compact display mode
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• cozyAlt?: boolean - Use cozy alternative display
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• isInline?: boolean - Display inline
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• isVisibleOnlyOnHover?: boolean - Only show on hover
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• isEdited?: boolean - Show "(edited)" indicator
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• tooltipPosition?: "top" | "bottom" | "left" | "right" - Tooltip position
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• id?: string - Element ID
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• className?: string - Additional CSS class
|
||||
</Paragraph>
|
||||
<Paragraph color="text-muted">
|
||||
• children?: ReactNode - Custom content
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
136
src/equicordplugins/components.dev/components/tabs/ToastTab.tsx
Normal file
136
src/equicordplugins/components.dev/components/tabs/ToastTab.tsx
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ManaButton, Paragraph, showToast, Toasts, ToastType, ToastTypeValue } from "..";
|
||||
import { SectionWrapper } from "../SectionWrapper";
|
||||
|
||||
const TOAST_TYPES = [
|
||||
{ type: ToastType.MESSAGE, label: "MESSAGE", description: "Default message toast" },
|
||||
{ type: ToastType.SUCCESS, label: "SUCCESS", description: "Green success toast" },
|
||||
{ type: ToastType.FAILURE, label: "FAILURE", description: "Red failure/error toast" },
|
||||
{ type: ToastType.CLIP, label: "CLIP", description: "Clip/copy toast" },
|
||||
{ type: ToastType.LINK, label: "LINK", description: "Link copied toast" },
|
||||
{ type: ToastType.FORWARD, label: "FORWARD", description: "Forward action toast" },
|
||||
{ type: ToastType.BOOKMARK, label: "BOOKMARK", description: "Bookmark action toast" },
|
||||
{ type: ToastType.CLOCK, label: "CLOCK", description: "Clock/timer toast" },
|
||||
] as const;
|
||||
|
||||
export default function ToastTab() {
|
||||
const showSimpleToast = (type: ToastTypeValue, label: string) => {
|
||||
showToast(`This is a ${label} toast!`, type);
|
||||
};
|
||||
|
||||
const showAdvancedToast = () => {
|
||||
const toast = Toasts.create("Advanced toast with options", ToastType.SUCCESS, {
|
||||
duration: 5000,
|
||||
position: Toasts.Position.BOTTOM,
|
||||
});
|
||||
Toasts.show(toast);
|
||||
};
|
||||
|
||||
const showCustomToast = () => {
|
||||
const toast = Toasts.create("Custom component toast", ToastType.CUSTOM, {
|
||||
component: <span style={{ color: "var(--text-brand)" }}>Custom JSX content!</span>,
|
||||
});
|
||||
Toasts.show(toast);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vc-compfinder-section">
|
||||
<SectionWrapper title="Toast Types">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Click buttons to show different toast types. Toasts appear at screen edges.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid" style={{ gap: 8 }}>
|
||||
{TOAST_TYPES.map(({ type, label, description }) => (
|
||||
<div key={label} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text={label}
|
||||
onClick={() => showSimpleToast(type, label)}
|
||||
/>
|
||||
<Paragraph color="text-muted" style={{ fontSize: 10 }}>
|
||||
{description}
|
||||
</Paragraph>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Simple Usage">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
showToast(message, type) - Quick way to show a toast.
|
||||
</Paragraph>
|
||||
<ManaButton
|
||||
variant="primary"
|
||||
text="Show Simple Toast"
|
||||
onClick={() => showToast("Hello from showToast!", ToastType.SUCCESS)}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Advanced Usage">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Toasts.show() with full options object.
|
||||
</Paragraph>
|
||||
<div className="vc-compfinder-grid" style={{ gap: 8 }}>
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text="Advanced Toast (5s duration)"
|
||||
onClick={showAdvancedToast}
|
||||
/>
|
||||
<ManaButton
|
||||
variant="secondary"
|
||||
text="Custom Component Toast"
|
||||
onClick={showCustomToast}
|
||||
/>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Pop Toast">
|
||||
<Paragraph color="text-muted" style={{ marginBottom: 8 }}>
|
||||
Toasts.pop() removes the most recent toast.
|
||||
</Paragraph>
|
||||
<ManaButton
|
||||
variant="critical-secondary"
|
||||
text="Pop Last Toast"
|
||||
onClick={() => Toasts.pop()}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ToastType">
|
||||
<Paragraph color="text-muted">• MESSAGE - Default message style</Paragraph>
|
||||
<Paragraph color="text-muted">• SUCCESS - Green checkmark icon</Paragraph>
|
||||
<Paragraph color="text-muted">• FAILURE - Red X icon</Paragraph>
|
||||
<Paragraph color="text-muted">• CUSTOM - Custom component content</Paragraph>
|
||||
<Paragraph color="text-muted">• CLIP - Clip/scissors icon</Paragraph>
|
||||
<Paragraph color="text-muted">• LINK - Link icon</Paragraph>
|
||||
<Paragraph color="text-muted">• FORWARD - Forward arrow icon</Paragraph>
|
||||
<Paragraph color="text-muted">• BOOKMARK - Bookmark icon</Paragraph>
|
||||
<Paragraph color="text-muted">• CLOCK - Clock icon</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="ToastPosition">
|
||||
<Paragraph color="text-muted">• TOP (0) - Toast appears at top</Paragraph>
|
||||
<Paragraph color="text-muted">• BOTTOM (1) - Toast appears at bottom</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Toasts.show() Options">
|
||||
<Paragraph color="text-muted">• message - Toast message text</Paragraph>
|
||||
<Paragraph color="text-muted">• type - Toast type from ToastType</Paragraph>
|
||||
<Paragraph color="text-muted">• id - Unique ID (use Toasts.genId())</Paragraph>
|
||||
<Paragraph color="text-muted">• options.duration - Display time in ms</Paragraph>
|
||||
<Paragraph color="text-muted">• options.position - ToastPosition value</Paragraph>
|
||||
<Paragraph color="text-muted">• options.component - Custom React component</Paragraph>
|
||||
</SectionWrapper>
|
||||
|
||||
<SectionWrapper title="Import">
|
||||
<Paragraph color="text-muted">
|
||||
{"import { showToast, Toasts, ToastType, ToastPosition } from \"../components\";"}
|
||||
</Paragraph>
|
||||
</SectionWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,20 +4,15 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { ManaButton, ManaTooltip } from "..";
|
||||
import { ManaButton, ManaTooltip, Paragraph, TooltipColors, TooltipPositions } 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 => (
|
||||
{TooltipPositions.map(position => (
|
||||
<ManaTooltip key={position} text={`Tooltip on ${position}`} position={position}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text={position} />
|
||||
|
|
@ -29,7 +24,7 @@ export default function TooltipTab() {
|
|||
|
||||
<SectionWrapper title="Colors">
|
||||
<div className="vc-compfinder-grid">
|
||||
{COLORS.map(color => (
|
||||
{TooltipColors.map(color => (
|
||||
<ManaTooltip key={color} text={`${color} tooltip`} color={color}>
|
||||
{props => (
|
||||
<ManaButton {...props} variant="secondary" text={color} />
|
||||
|
|
|
|||
|
|
@ -4,69 +4,15 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
|
||||
import { CodeColors, FontSizeMap, Paragraph, TextColors, TextSizes, TextWeights } from "..";
|
||||
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 => (
|
||||
{TextColors.map(color => (
|
||||
<div key={color} style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
||||
<span style={{ color: `var(--${color})`, minWidth: 200 }}>
|
||||
{color}
|
||||
|
|
@ -81,13 +27,13 @@ export default function TypographyTab() {
|
|||
|
||||
<SectionWrapper title="Text Sizes">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{TEXT_SIZES.map(({ name, value }) => (
|
||||
{TextSizes.map(({ name, value, pixels }) => (
|
||||
<div key={name} style={{ display: "flex", alignItems: "baseline", gap: 16 }}>
|
||||
<span style={{ fontSize: FONT_SIZE_MAP[name], minWidth: 200 }}>
|
||||
<span style={{ fontSize: FontSizeMap[name], minWidth: 200 }}>
|
||||
{name}
|
||||
</span>
|
||||
<code style={{ color: "var(--text-muted)", fontSize: "0.75rem" }}>
|
||||
{value}
|
||||
{value} ({pixels})
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -96,7 +42,7 @@ export default function TypographyTab() {
|
|||
|
||||
<SectionWrapper title="Text Weights">
|
||||
<div className="vc-compfinder-grid-vertical">
|
||||
{TEXT_WEIGHTS.map(({ name, value }) => (
|
||||
{TextWeights.map(({ name, value }) => (
|
||||
<div key={name} style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
||||
<span style={{ fontWeight: value, minWidth: 200 }}>
|
||||
{name}
|
||||
|
|
@ -122,20 +68,7 @@ export default function TypographyTab() {
|
|||
|
||||
<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 => (
|
||||
{CodeColors.map(color => (
|
||||
<div key={color} style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
||||
<span style={{ color: `var(--${color})`, minWidth: 200, fontFamily: "monospace" }}>
|
||||
{color}
|
||||
|
|
|
|||
167
src/equicordplugins/components.dev/constants.ts
Normal file
167
src/equicordplugins/components.dev/constants.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export const ManaButtonVariants = ["primary", "secondary", "critical-primary", "critical-secondary", "overlay-primary", "overlay-secondary", "expressive"] as const;
|
||||
export type ManaButtonVariant = typeof ManaButtonVariants[number];
|
||||
|
||||
export const ManaButtonSizes = ["xs", "sm", "md"] as const;
|
||||
export type ManaButtonSize = typeof ManaButtonSizes[number];
|
||||
|
||||
export const ManaTextButtonVariants = ["primary", "secondary", "always-white", "critical"] as const;
|
||||
export type ManaTextButtonVariant = typeof ManaTextButtonVariants[number];
|
||||
|
||||
export const AvatarSizes = ["SIZE_16", "SIZE_20", "SIZE_24", "SIZE_32", "SIZE_40", "SIZE_44", "SIZE_48", "SIZE_56", "SIZE_72", "SIZE_80", "SIZE_96", "SIZE_120", "SIZE_152"] as const;
|
||||
export type AvatarSize = typeof AvatarSizes[number];
|
||||
|
||||
export const AvatarStatuses = ["online", "idle", "dnd", "offline", "streaming"] as const;
|
||||
export type AvatarStatus = typeof AvatarStatuses[number];
|
||||
|
||||
export const GuildIconSizes = [16, 20, 24, 32, 40, 48, 56, 80, 96] as const;
|
||||
export type GuildIconSize = typeof GuildIconSizes[number];
|
||||
|
||||
export const CardVariants = ["normal", "warning", "danger"] as const;
|
||||
export type CardVariant = typeof CardVariants[number];
|
||||
|
||||
export const SpinnerTypes = ["wanderingCubes", "chasingDots", "pulsingEllipsis", "spinningCircle", "spinningCircleSimple", "lowMotion"] as const;
|
||||
export type SpinnerType = typeof SpinnerTypes[number];
|
||||
|
||||
export const TooltipPositions = ["top", "bottom", "left", "right"] as const;
|
||||
export type TooltipPosition = typeof TooltipPositions[number];
|
||||
|
||||
export const TooltipAligns = ["start", "center", "end"] as const;
|
||||
export type TooltipAlign = typeof TooltipAligns[number];
|
||||
|
||||
export const TooltipColors = ["primary", "black", "grey", "brand", "green", "yellow", "red"] as const;
|
||||
export type TooltipColor = typeof TooltipColors[number];
|
||||
|
||||
export const TextInputSizes = ["sm", "md"] as const;
|
||||
export type TextInputSize = typeof TextInputSizes[number];
|
||||
|
||||
export const TimestampFormats = [
|
||||
{ format: "R", label: "Relative", example: "2 hours ago" },
|
||||
{ format: "t", label: "Short Time", example: "9:30 PM" },
|
||||
{ format: "T", label: "Long Time", example: "9:30:00 PM" },
|
||||
{ format: "d", label: "Short Date", example: "01/20/2024" },
|
||||
{ format: "D", label: "Long Date", example: "January 20, 2024" },
|
||||
{ format: "f", label: "Short DateTime", example: "January 20, 2024 9:30 PM" },
|
||||
{ format: "F", label: "Long DateTime", example: "Saturday, January 20, 2024 9:30 PM" },
|
||||
] as const;
|
||||
|
||||
export const ModalSizes = [
|
||||
{ size: "small", width: "440px" },
|
||||
{ size: "medium", width: "600px" },
|
||||
{ size: "large", width: "720px" },
|
||||
{ size: "dynamic", width: "fit-content" },
|
||||
] as const;
|
||||
export type ModalSize = typeof ModalSizes[number]["size"];
|
||||
|
||||
export const TextColors = [
|
||||
"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;
|
||||
export type TextColorType = typeof TextColors[number];
|
||||
|
||||
export const TextSizes = [
|
||||
{ name: "xxs", value: "0.625rem", pixels: "10px" },
|
||||
{ name: "xs", value: "0.75rem", pixels: "12px" },
|
||||
{ name: "sm", value: "0.875rem", pixels: "14px" },
|
||||
{ name: "md", value: "1rem", pixels: "16px" },
|
||||
{ name: "lg", value: "1.25rem", pixels: "20px" },
|
||||
{ name: "xl", value: "1.5rem", pixels: "24px" },
|
||||
{ name: "xxl", value: "2rem", pixels: "32px" },
|
||||
] as const;
|
||||
export type TextSizeType = typeof TextSizes[number]["name"];
|
||||
|
||||
export const TextWeights = [
|
||||
{ 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;
|
||||
export type TextWeightType = typeof TextWeights[number]["name"];
|
||||
|
||||
export const TextVariants = [
|
||||
"text-xxl/bold", "text-xxl/semibold", "text-xxl/medium", "text-xxl/normal",
|
||||
"text-xl/bold", "text-xl/semibold", "text-xl/medium", "text-xl/normal",
|
||||
"text-lg/bold", "text-lg/semibold", "text-lg/medium", "text-lg/normal",
|
||||
"text-md/bold", "text-md/semibold", "text-md/medium", "text-md/normal",
|
||||
"text-sm/bold", "text-sm/semibold", "text-sm/medium", "text-sm/normal",
|
||||
"text-xs/bold", "text-xs/semibold", "text-xs/medium", "text-xs/normal",
|
||||
] as const;
|
||||
export type TextVariantType = typeof TextVariants[number];
|
||||
|
||||
export const HeadingVariants = [
|
||||
"heading-xxl/extrabold", "heading-xxl/bold", "heading-xxl/semibold", "heading-xxl/medium", "heading-xxl/normal",
|
||||
"heading-xl/extrabold", "heading-xl/bold", "heading-xl/semibold", "heading-xl/medium",
|
||||
"heading-lg/extrabold", "heading-lg/bold", "heading-lg/semibold", "heading-lg/medium",
|
||||
"heading-md/bold", "heading-md/semibold", "heading-md/medium", "heading-md/normal",
|
||||
"heading-sm/bold", "heading-sm/semibold", "heading-sm/medium",
|
||||
] as const;
|
||||
export type HeadingVariantType = typeof HeadingVariants[number];
|
||||
|
||||
export const CodeColors = [
|
||||
"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",
|
||||
] as const;
|
||||
export type CodeColorType = typeof CodeColors[number];
|
||||
|
||||
export const NoticeTypes = ["info", "warn", "danger", "positive", "preview"] as const;
|
||||
export type NoticeTypeValue = typeof NoticeTypes[number];
|
||||
|
||||
export const PresetColors = [
|
||||
0x5865F2, 0x3BA55C, 0xFAA61A, 0xED4245, 0xEB459E,
|
||||
0x9B59B6, 0x3498DB, 0x1ABC9C, 0xE67E22, 0x2ECC71,
|
||||
] as const;
|
||||
|
||||
export const FontSizeMap: Record<string, string> = {
|
||||
xxs: "0.625rem",
|
||||
xs: "0.75rem",
|
||||
sm: "0.875rem",
|
||||
md: "1rem",
|
||||
lg: "1.25rem",
|
||||
xl: "1.5rem",
|
||||
xxl: "2rem",
|
||||
};
|
||||
|
||||
export const ManaTextButtonTextVariants = [
|
||||
"text-xs/normal", "text-sm/normal", "text-sm/medium",
|
||||
"text-md/normal", "text-md/medium", "text-lg/medium",
|
||||
] as const;
|
||||
export type ManaTextButtonTextVariant = typeof ManaTextButtonTextVariants[number];
|
||||
|
||||
export const TimestampDisplayFormats = [
|
||||
{ format: "LT", description: "Time (e.g., 8:30 PM)" },
|
||||
{ format: "LTS", description: "Time with seconds (e.g., 8:30:25 PM)" },
|
||||
{ format: "L", description: "Date (e.g., 09/04/1986)" },
|
||||
{ format: "LL", description: "Date with month name (e.g., September 4, 1986)" },
|
||||
{ format: "LLL", description: "Date and time (e.g., September 4, 1986 8:30 PM)" },
|
||||
{ format: "LLLL", description: "Full date and time (e.g., Thursday, September 4, 1986 8:30 PM)" },
|
||||
{ format: "l", description: "Short date (e.g., 9/4/1986)" },
|
||||
{ format: "ll", description: "Short date with month (e.g., Sep 4, 1986)" },
|
||||
{ format: "lll", description: "Short date and time (e.g., Sep 4, 1986 8:30 PM)" },
|
||||
{ format: "llll", description: "Short full date and time (e.g., Thu, Sep 4, 1986 8:30 PM)" },
|
||||
] as const;
|
||||
export type TimestampDisplayFormat = typeof TimestampDisplayFormats[number]["format"];
|
||||
|
||||
export const CheckboxLabelTypes = ["primary", "secondary"] as const;
|
||||
export type CheckboxLabelType = typeof CheckboxLabelTypes[number];
|
||||
|
||||
export const CheckboxUsageVariants = ["single", "indicator"] as const;
|
||||
export type CheckboxUsageVariant = typeof CheckboxUsageVariants[number];
|
||||
|
||||
export const PopoverSizes = ["sm", "md", "lg"] as const;
|
||||
export type PopoverSize = typeof PopoverSizes[number];
|
||||
|
||||
export const SliderOrientations = ["horizontal", "vertical"] as const;
|
||||
export type SliderOrientation = typeof SliderOrientations[number];
|
||||
18
src/equicordplugins/components.dev/types/accordion.d.ts
vendored
Normal file
18
src/equicordplugins/components.dev/types/accordion.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 interface AccordionProps {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
isExpanded?: boolean;
|
||||
onExpandedChange?: (expanded: boolean) => void;
|
||||
defaultExpanded?: boolean;
|
||||
onOpen?: () => void;
|
||||
maxHeight?: number | string;
|
||||
}
|
||||
|
|
@ -4,9 +4,16 @@
|
|||
* 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";
|
||||
import type { AvatarSize, AvatarStatus } from "../constants";
|
||||
|
||||
export type AvatarStatus = "online" | "idle" | "dnd" | "offline" | "streaming";
|
||||
export type { AvatarSize, AvatarStatus };
|
||||
|
||||
export interface AvatarSizeConfig {
|
||||
size: number;
|
||||
status: number;
|
||||
stroke: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface AvatarProps {
|
||||
src?: string;
|
||||
|
|
@ -18,8 +25,10 @@ export interface AvatarProps {
|
|||
isSpeaking?: boolean;
|
||||
statusTooltip?: boolean;
|
||||
statusTooltipDelay?: number;
|
||||
avatarDecoration?: string;
|
||||
"aria-hidden"?: boolean;
|
||||
"aria-label"?: string;
|
||||
imageClassName?: string;
|
||||
className?: string;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,26 +4,57 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { ManaButtonSize, ManaButtonVariant, ManaTextButtonVariant } from "../constants";
|
||||
|
||||
export type { ManaButtonSize, ManaButtonVariant, ManaTextButtonVariant };
|
||||
|
||||
export type ManaButtonIconType =
|
||||
| React.ComponentType<any>
|
||||
| { type: "icon"; asset: React.ComponentType<any>; }
|
||||
| { type: "rive"; asset: React.ComponentType<any>; riveProps?: Record<string, any>; }
|
||||
| { type: "sticker"; asset: any; component: React.ComponentType<any>; };
|
||||
|
||||
export interface ManaButtonProps {
|
||||
text?: string;
|
||||
variant?: "primary" | "secondary" | "critical-primary" | "critical-secondary" | "overlay-primary" | "overlay-secondary" | "expressive";
|
||||
size?: "xs" | "sm" | "md";
|
||||
variant?: ManaButtonVariant;
|
||||
size?: ManaButtonSize;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
loadingStartedLabel?: string;
|
||||
loadingFinishedLabel?: string;
|
||||
fullWidth?: boolean;
|
||||
rounded?: boolean;
|
||||
icon?: React.ComponentType<any>;
|
||||
icon?: ManaButtonIconType;
|
||||
iconPosition?: "start" | "end";
|
||||
onClick?: () => void;
|
||||
iconOpticalOffsetMargin?: number;
|
||||
minWidth?: number | string;
|
||||
role?: string;
|
||||
type?: "button" | "submit" | "reset";
|
||||
rel?: string;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
onDoubleClick?: (e: React.MouseEvent) => void;
|
||||
onMouseEnter?: (e: React.MouseEvent) => void;
|
||||
onMouseLeave?: (e: React.MouseEvent) => void;
|
||||
onMouseUp?: (e: React.MouseEvent) => void;
|
||||
onMouseDown?: (e: React.MouseEvent) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent) => void;
|
||||
focusProps?: Record<string, any>;
|
||||
buttonRef?: React.Ref<HTMLButtonElement>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface ManaTextButtonProps {
|
||||
text: string;
|
||||
variant?: "primary" | "secondary" | "always-white" | "critical";
|
||||
variant?: ManaTextButtonVariant;
|
||||
textVariant?: string;
|
||||
lineClamp?: number;
|
||||
onClick?: () => void;
|
||||
role?: string;
|
||||
type?: "button" | "submit" | "reset";
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
disabled?: boolean;
|
||||
focusProps?: Record<string, any>;
|
||||
buttonRef?: React.Ref<HTMLButtonElement>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { CheckboxLabelType, CheckboxUsageVariant } from "../constants";
|
||||
|
||||
export type { CheckboxLabelType, CheckboxUsageVariant };
|
||||
|
||||
export interface ManaCheckboxProps {
|
||||
checked: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
|
|
@ -12,8 +16,8 @@ export interface ManaCheckboxProps {
|
|||
label?: string;
|
||||
description?: string;
|
||||
leadingIcon?: React.ComponentType<any>;
|
||||
labelType?: "primary" | "secondary";
|
||||
usageVariant?: "single" | "indicator";
|
||||
labelType?: CheckboxLabelType;
|
||||
usageVariant?: CheckboxUsageVariant;
|
||||
}
|
||||
|
||||
export interface CheckboxGroupOption {
|
||||
|
|
|
|||
20
src/equicordplugins/components.dev/types/chip.d.ts
vendored
Normal file
20
src/equicordplugins/components.dev/types/chip.d.ts
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type ChipVariant =
|
||||
| "blurpleLight" | "blurpleMedium" | "blurpleDark"
|
||||
| "purpleLight" | "purpleMedium" | "purpleDark"
|
||||
| "greenLight" | "greenMedium" | "greenDark"
|
||||
| "orangeLight" | "orangeMedium" | "orangeDark"
|
||||
| "yellowLight" | "yellowMedium" | "yellowDark"
|
||||
| "pinkLight" | "pinkMedium" | "pinkDark"
|
||||
| "redLight" | "redMedium" | "redDark"
|
||||
| "grayLight" | "grayMedium" | "grayDark";
|
||||
|
||||
export interface ChipProps {
|
||||
text: string;
|
||||
variant?: ChipVariant;
|
||||
}
|
||||
15
src/equicordplugins/components.dev/types/guildicon.d.ts
vendored
Normal file
15
src/equicordplugins/components.dev/types/guildicon.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 interface GuildIconProps {
|
||||
guildId: string;
|
||||
guildName: string;
|
||||
guildIcon: string | null;
|
||||
iconSize: number;
|
||||
className?: string;
|
||||
acronymClassName?: string;
|
||||
animate?: boolean;
|
||||
}
|
||||
32
src/equicordplugins/components.dev/types/heading.d.ts
vendored
Normal file
32
src/equicordplugins/components.dev/types/heading.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
|
||||
*/
|
||||
|
||||
import type { HeadingVariantType, TextColorType, TextVariantType } from "../constants";
|
||||
|
||||
export type HeadingVariant = HeadingVariantType;
|
||||
export type TextVariant = TextVariantType;
|
||||
export type TextColor = TextColorType | "currentColor" | "none" | "always-white";
|
||||
|
||||
export interface DiscordHeadingProps {
|
||||
variant: HeadingVariant;
|
||||
className?: string;
|
||||
color?: TextColor;
|
||||
style?: React.CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface DiscordTextProps {
|
||||
variant: TextVariant;
|
||||
color?: TextColor;
|
||||
tag?: string;
|
||||
selectable?: boolean;
|
||||
lineClamp?: number;
|
||||
tabularNumbers?: boolean;
|
||||
scaleFontToUserSetting?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
|
@ -4,22 +4,32 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export * from "../constants";
|
||||
export * from "./accordion";
|
||||
export * from "./anchor";
|
||||
export * from "./avatar";
|
||||
export * from "./badge";
|
||||
export * from "./button";
|
||||
export * from "./calendar";
|
||||
export * from "./checkbox";
|
||||
export * from "./chip";
|
||||
export * from "./clickable";
|
||||
export * from "./colorpicker";
|
||||
export * from "./guildicon.d";
|
||||
export * from "./heading";
|
||||
export * from "./input";
|
||||
export * from "./modal";
|
||||
export * from "./notice";
|
||||
export * from "./paginator";
|
||||
export * from "./progress";
|
||||
export * from "./radio";
|
||||
export * from "./scroller";
|
||||
export * from "./select";
|
||||
export * from "./skeleton";
|
||||
export * from "./slider";
|
||||
export * from "./spinner";
|
||||
export * from "./switch";
|
||||
export * from "./tabbar";
|
||||
export * from "./timestamp";
|
||||
export * from "./toast";
|
||||
export * from "./tooltip";
|
||||
|
|
|
|||
|
|
@ -4,29 +4,76 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { TextInputSize } from "../constants";
|
||||
|
||||
export type { TextInputSize };
|
||||
|
||||
export type TextInputLeadingAccessory =
|
||||
| string
|
||||
| React.ComponentType<any>
|
||||
| { icon: React.ComponentType<any>; tooltip?: string; }
|
||||
| { button: React.ReactNode; }
|
||||
| { type: "tags"; tags: string[]; onRemove?: (tag: string) => void; }
|
||||
| { type: "image"; src: string; };
|
||||
|
||||
export type TextInputTrailingAccessory =
|
||||
| React.ComponentType<any>
|
||||
| { icon: React.ComponentType<any>; tooltip?: string; }
|
||||
| { button: React.ReactNode; };
|
||||
|
||||
export interface ManaTextInputProps {
|
||||
value: string;
|
||||
onChange?: (value: string) => void;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
onChange?: (value: string, name?: string) => void;
|
||||
onClear?: (e: React.MouseEvent) => void;
|
||||
onFocus?: (e: React.FocusEvent) => void;
|
||||
onBlur?: (e: React.FocusEvent) => void;
|
||||
placeholder?: string;
|
||||
name?: string;
|
||||
type?: "text" | "password" | "email" | "number" | "search" | "tel" | "url";
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
size?: "sm" | "md";
|
||||
error?: boolean;
|
||||
clearable?: boolean;
|
||||
editable?: boolean;
|
||||
size?: TextInputSize;
|
||||
error?: string | boolean;
|
||||
clearable?: boolean | { show: boolean; };
|
||||
showCharacterCount?: boolean;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
fullWidth?: boolean;
|
||||
leading?: TextInputLeadingAccessory;
|
||||
trailing?: TextInputTrailingAccessory;
|
||||
validateOn?: "change" | "blur";
|
||||
defaultDirty?: boolean;
|
||||
focusProps?: Record<string, any>;
|
||||
inputRef?: React.Ref<HTMLInputElement>;
|
||||
"aria-label"?: string;
|
||||
"aria-labelledby"?: string;
|
||||
"aria-describedby"?: string;
|
||||
}
|
||||
|
||||
export interface ManaTextAreaProps {
|
||||
value: string;
|
||||
onChange?: (value: string) => void;
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
onChange?: (value: string, name?: string) => void;
|
||||
onFocus?: (e: React.FocusEvent) => void;
|
||||
onBlur?: (e: React.FocusEvent) => void;
|
||||
placeholder?: string;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
rows?: number;
|
||||
autosize?: boolean;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
showCharacterCount?: boolean;
|
||||
error?: string | boolean;
|
||||
fullWidth?: boolean;
|
||||
focusProps?: Record<string, any>;
|
||||
inputRef?: React.Ref<HTMLTextAreaElement>;
|
||||
"aria-label"?: string;
|
||||
"aria-labelledby"?: string;
|
||||
"aria-describedby"?: string;
|
||||
}
|
||||
|
||||
export interface SearchBarProps {
|
||||
|
|
@ -34,7 +81,7 @@ export interface SearchBarProps {
|
|||
onChange: (value: string) => void;
|
||||
onClear?: () => void;
|
||||
placeholder?: string;
|
||||
size?: "sm" | "md";
|
||||
size?: TextInputSize;
|
||||
autoFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
onKeyDown?: (e: React.KeyboardEvent) => void;
|
||||
|
|
|
|||
94
src/equicordplugins/components.dev/types/modal.d.ts
vendored
Normal file
94
src/equicordplugins/components.dev/types/modal.d.ts
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type NativeModalSize = "sm" | "md";
|
||||
|
||||
export type ModalActionVariant = "primary" | "secondary" | "critical-primary" | "critical-secondary";
|
||||
|
||||
export interface ModalAction {
|
||||
text: string;
|
||||
variant: ModalActionVariant;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export interface ModalNotice {
|
||||
message: string;
|
||||
type: "info" | "warn" | "critical" | "positive";
|
||||
}
|
||||
|
||||
export interface ModalProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
size?: NativeModalSize;
|
||||
transitionState?: number;
|
||||
children?: React.ReactNode;
|
||||
actions?: ModalAction[];
|
||||
actionBarInput?: React.ReactNode;
|
||||
actionBarInputLayout?: "default" | "full-width";
|
||||
input?: React.ReactNode;
|
||||
preview?: React.ReactNode;
|
||||
listProps?: Record<string, any>;
|
||||
notice?: ModalNotice;
|
||||
onClose: () => void;
|
||||
role?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface ConfirmModalProps extends Omit<ModalProps, "actions" | "transitionState"> {
|
||||
transitionState?: number;
|
||||
confirmText: string;
|
||||
cancelText?: string;
|
||||
onConfirm?: (setError?: (error: string) => void) => void | Promise<void>;
|
||||
onCancel?: () => void;
|
||||
onCloseCallback?: () => void;
|
||||
variant?: "critical" | "primary" | "secondary";
|
||||
checkboxProps?: {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExpressiveModalProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
transitionState?: number;
|
||||
graphic?: { src: string; aspectRatio?: string };
|
||||
badge?: React.ReactNode;
|
||||
actions?: ModalAction[];
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export interface OpenModalOptions {
|
||||
modalKey?: string;
|
||||
dismissable?: boolean;
|
||||
instant?: boolean;
|
||||
onCloseRequest?: () => void;
|
||||
onCloseCallback?: () => void;
|
||||
backdropStyle?: "default" | "dark" | "transparent";
|
||||
stackingBehavior?: "stack" | "replace";
|
||||
stackNextByDefault?: boolean;
|
||||
allowsNavigation?: boolean;
|
||||
}
|
||||
|
||||
export type ModalRenderFn = (props: { onClose: () => void; transitionState: number }) => React.ReactNode;
|
||||
|
||||
export interface ModalAPI {
|
||||
openModal: (render: ModalRenderFn, options?: OpenModalOptions) => string;
|
||||
openModalLazy: (renderFactory: () => Promise<{ default: React.ComponentType<any> }>, options?: OpenModalOptions) => Promise<string>;
|
||||
closeModal: (modalKey: string) => void;
|
||||
closeAllModals: () => void;
|
||||
hasModalOpen: (modalKey: string) => boolean;
|
||||
hasAnyModalOpen: () => boolean;
|
||||
}
|
||||
|
||||
export interface ModalComponents {
|
||||
Modal: React.ComponentType<ModalProps>;
|
||||
ConfirmModal: React.ComponentType<ConfirmModalProps>;
|
||||
ExpressiveModal: React.ComponentType<ExpressiveModalProps>;
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type NoticeType = "warn" | "info" | "danger" | "positive" | "preview";
|
||||
import type { NoticeTypeValue } from "../constants";
|
||||
|
||||
export type NoticeType = NoticeTypeValue;
|
||||
|
||||
export interface NoticeProps {
|
||||
children: React.ReactNode;
|
||||
|
|
|
|||
24
src/equicordplugins/components.dev/types/scroller.d.ts
vendored
Normal file
24
src/equicordplugins/components.dev/types/scroller.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 ScrollerProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
orientation?: "vertical" | "horizontal";
|
||||
fade?: boolean;
|
||||
dir?: "ltr" | "rtl";
|
||||
onScroll?: (e: React.UIEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
|
||||
export interface ListScrollerProps extends ScrollerProps {
|
||||
rowHeight: number;
|
||||
sections: number[];
|
||||
sectionHeight: number;
|
||||
renderSection: (section: number) => React.ReactNode;
|
||||
renderRow: (row: { section: number; row: number }) => React.ReactNode;
|
||||
role?: string;
|
||||
}
|
||||
9
src/equicordplugins/components.dev/types/skeleton.d.ts
vendored
Normal file
9
src/equicordplugins/components.dev/types/skeleton.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface SkeletonProps {
|
||||
skipPulseAnimation?: boolean;
|
||||
}
|
||||
|
|
@ -4,25 +4,38 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { SliderOrientation } from "../constants";
|
||||
|
||||
export type { SliderOrientation };
|
||||
|
||||
export interface SliderProps {
|
||||
initialValue?: number;
|
||||
value?: number;
|
||||
minValue?: number;
|
||||
maxValue?: number;
|
||||
onValueChange?: (value: number) => void;
|
||||
onValueRender?: (value: number) => string;
|
||||
asValueChanges?: (value: number) => void;
|
||||
onValueRender?: (value: number) => string | null;
|
||||
markers?: number[];
|
||||
stickToMarkers?: boolean;
|
||||
onMarkerRender?: (value: number) => string;
|
||||
equidistant?: boolean;
|
||||
onMarkerRender?: (value: number) => string | React.ReactNode | null;
|
||||
renderMarker?: (value: number) => React.ReactNode;
|
||||
getAriaValueText?: (value: number) => string;
|
||||
keyboardStep?: number;
|
||||
disabled?: boolean;
|
||||
mini?: boolean;
|
||||
hideBubble?: boolean;
|
||||
defaultValue?: number;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
orientation?: SliderOrientation;
|
||||
markerPosition?: 0 | 1;
|
||||
barStyles?: React.CSSProperties;
|
||||
fillStyles?: React.CSSProperties;
|
||||
grabberStyles?: React.CSSProperties;
|
||||
barClassName?: string;
|
||||
grabberClassName?: string;
|
||||
className?: string;
|
||||
"aria-hidden"?: boolean;
|
||||
"aria-label"?: string;
|
||||
"aria-labelledby"?: string;
|
||||
"aria-describedby"?: string;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,18 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type SpinnerType = "wanderingCubes" | "chasingDots" | "pulsingEllipsis" | "spinningCircle" | "spinningCircleSimple" | "lowMotion";
|
||||
import type { SpinnerType } from "../constants";
|
||||
|
||||
export type { SpinnerType };
|
||||
|
||||
export interface SpinnerTypeEnum {
|
||||
WANDERING_CUBES: "wanderingCubes";
|
||||
CHASING_DOTS: "chasingDots";
|
||||
PULSING_ELLIPSIS: "pulsingEllipsis";
|
||||
SPINNING_CIRCLE: "spinningCircle";
|
||||
SPINNING_CIRCLE_SIMPLE: "spinningCircleSimple";
|
||||
LOW_MOTION: "lowMotion";
|
||||
}
|
||||
|
||||
export interface SpinnerProps {
|
||||
type?: SpinnerType;
|
||||
|
|
@ -13,3 +24,7 @@ export interface SpinnerProps {
|
|||
itemClassName?: string;
|
||||
"aria-label"?: string;
|
||||
}
|
||||
|
||||
export interface SpinnerComponent extends React.FC<SpinnerProps> {
|
||||
Type: SpinnerTypeEnum;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,10 @@ export interface ManaSwitchProps {
|
|||
checked: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
hasIcon?: boolean;
|
||||
focusProps?: Record<string, any>;
|
||||
describedBy?: string;
|
||||
labelledBy?: string;
|
||||
innerRef?: React.Ref<HTMLInputElement>;
|
||||
}
|
||||
|
|
|
|||
27
src/equicordplugins/components.dev/types/timestamp.d.ts
vendored
Normal file
27
src/equicordplugins/components.dev/types/timestamp.d.ts
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Moment } from "moment";
|
||||
|
||||
import type { TimestampDisplayFormat, TooltipPosition } from "../constants";
|
||||
|
||||
export type TimestampFormat = TimestampDisplayFormat;
|
||||
|
||||
export type { TooltipPosition };
|
||||
|
||||
export interface TimestampProps {
|
||||
timestamp: Date | Moment;
|
||||
timestampFormat?: TimestampFormat;
|
||||
compact?: boolean;
|
||||
cozyAlt?: boolean;
|
||||
isInline?: boolean;
|
||||
isVisibleOnlyOnHover?: boolean;
|
||||
isEdited?: boolean;
|
||||
id?: string;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
tooltipPosition?: TooltipPosition;
|
||||
}
|
||||
32
src/equicordplugins/components.dev/types/toast.d.ts
vendored
Normal file
32
src/equicordplugins/components.dev/types/toast.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 type ToastTypeValue = "message" | "success" | "failure" | "custom" | "clip" | "link" | "forward" | "bookmark" | "clock";
|
||||
|
||||
export type ToastPositionValue = 0 | 1;
|
||||
|
||||
export interface ToastOptions {
|
||||
duration?: number;
|
||||
position?: ToastPositionValue;
|
||||
component?: React.ReactNode;
|
||||
appContext?: number;
|
||||
}
|
||||
|
||||
export interface ToastData {
|
||||
message: string;
|
||||
type: ToastTypeValue;
|
||||
id: string;
|
||||
options?: ToastOptions;
|
||||
}
|
||||
|
||||
export interface ToastsModule {
|
||||
Type: Record<string, ToastTypeValue>;
|
||||
Position: Record<string, ToastPositionValue>;
|
||||
genId: () => string;
|
||||
show: (data: ToastData) => void;
|
||||
pop: () => void;
|
||||
create: (message: string, type: ToastTypeValue, options?: ToastOptions) => ToastData;
|
||||
}
|
||||
|
|
@ -4,11 +4,15 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { PopoverSize, TooltipAlign, TooltipColor, TooltipPosition } from "../constants";
|
||||
|
||||
export type { PopoverSize, TooltipAlign, TooltipColor, TooltipPosition };
|
||||
|
||||
export interface ManaTooltipProps {
|
||||
text: string;
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
align?: "start" | "center" | "end";
|
||||
color?: "primary" | "black" | "grey" | "brand" | "green" | "yellow" | "red";
|
||||
position?: TooltipPosition;
|
||||
align?: TooltipAlign;
|
||||
color?: TooltipColor;
|
||||
spacing?: number;
|
||||
hideOnClick?: boolean;
|
||||
delay?: number;
|
||||
|
|
@ -22,8 +26,8 @@ export interface ManaRichTooltipProps {
|
|||
assetSize?: number;
|
||||
asContainer?: boolean;
|
||||
element?: "span" | "div";
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
align?: "start" | "center" | "end";
|
||||
position?: TooltipPosition;
|
||||
align?: TooltipAlign;
|
||||
spacing?: number;
|
||||
ariaHidden?: boolean;
|
||||
children: React.ReactNode | ((props: any) => React.ReactNode);
|
||||
|
|
@ -40,14 +44,14 @@ export interface ManaPopoverProps {
|
|||
body?: string;
|
||||
badge?: React.ReactNode;
|
||||
graphic?: { src: string; aspectRatio?: string; };
|
||||
size?: "sm" | "md" | "lg";
|
||||
size?: PopoverSize;
|
||||
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";
|
||||
position?: TooltipPosition;
|
||||
alignmentStrategy?: "edge" | "trigger-center";
|
||||
align?: "left" | "center" | "right";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { BackupRestoreIcon, CloudIcon, LogIcon, MainSettingsIcon, PaintbrushIcon, PatchHelperIcon, PluginsIcon, UpdaterIcon, VesktopSettingsIcon } from "@components/Icons";
|
||||
import { BackupRestoreIcon, CloudIcon, LogIcon, MainSettingsIcon, PaintbrushIcon, PatchHelperIcon, PluginsIcon, UpdaterIcon } from "@components/Icons";
|
||||
import {
|
||||
BackupAndRestoreTab,
|
||||
ChangelogTab,
|
||||
|
|
@ -21,24 +21,39 @@ import { Devs } from "@utils/constants";
|
|||
import { getIntlMessage } from "@utils/discord";
|
||||
import { isTruthy } from "@utils/guards";
|
||||
import definePlugin, { IconProps, OptionType } from "@utils/types";
|
||||
import { waitFor } from "@webpack";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { React } from "@webpack/common";
|
||||
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
|
||||
|
||||
let LayoutTypes = {
|
||||
SECTION: 1,
|
||||
SIDEBAR_ITEM: 2,
|
||||
PANEL: 3,
|
||||
PANE: 4
|
||||
};
|
||||
waitFor(["SECTION", "SIDEBAR_ITEM", "PANEL"], v => LayoutTypes = v);
|
||||
const enum LayoutType {
|
||||
ROOT = 0,
|
||||
SECTION = 1,
|
||||
SIDEBAR_ITEM = 2,
|
||||
PANEL = 3,
|
||||
SPLIT = 4,
|
||||
CATEGORY = 5,
|
||||
ACCORDION = 6,
|
||||
LIST = 7,
|
||||
RELATED = 8,
|
||||
FIELD_SET = 9,
|
||||
TAB_ITEM = 10,
|
||||
STATIC = 11,
|
||||
BUTTON = 12,
|
||||
TOGGLE = 13,
|
||||
SLIDER = 14,
|
||||
SELECT = 15,
|
||||
RADIO = 16,
|
||||
NAVIGATOR = 17,
|
||||
CUSTOM = 18
|
||||
}
|
||||
|
||||
const FallbackSectionTypes = {
|
||||
HEADER: "HEADER",
|
||||
DIVIDER: "DIVIDER",
|
||||
CUSTOM: "CUSTOM"
|
||||
};
|
||||
type SectionTypes = typeof FallbackSectionTypes;
|
||||
const LayoutTypes: typeof LayoutType = findByPropsLazy("SECTION", "SIDEBAR_ITEM", "PANEL");
|
||||
|
||||
const enum SectionType {
|
||||
HEADER = "HEADER",
|
||||
DIVIDER = "DIVIDER",
|
||||
CUSTOM = "CUSTOM"
|
||||
}
|
||||
|
||||
type SettingsLocation =
|
||||
| "top"
|
||||
|
|
@ -49,7 +64,7 @@ type SettingsLocation =
|
|||
| "bottom";
|
||||
|
||||
interface SettingsLayoutNode {
|
||||
type: number;
|
||||
type: LayoutType;
|
||||
key?: string;
|
||||
legacySearchKey?: string;
|
||||
getLegacySearchKey?(): string;
|
||||
|
|
@ -62,12 +77,13 @@ interface SettingsLayoutNode {
|
|||
}
|
||||
|
||||
interface EntryOptions {
|
||||
key: string,
|
||||
title: string,
|
||||
panelTitle?: string,
|
||||
Component: ComponentType<{}>,
|
||||
key: string;
|
||||
title: string;
|
||||
panelTitle?: string;
|
||||
Component: ComponentType<{}>;
|
||||
Icon: ComponentType<IconProps>;
|
||||
}
|
||||
|
||||
interface SettingsLayoutBuilder {
|
||||
key?: string;
|
||||
buildLayout(): SettingsLayoutNode[];
|
||||
|
|
@ -100,7 +116,6 @@ export default definePlugin({
|
|||
{
|
||||
find: ".versionHash",
|
||||
replacement: [
|
||||
// for whatever reason the first letter of RELEASE_CHANNEL is now lowercase, so we fix that here because it looks better imo
|
||||
{
|
||||
match: /\.RELEASE_CHANNEL/,
|
||||
replace: "$&.replace(/^./, c => c.toUpperCase())"
|
||||
|
|
@ -130,13 +145,7 @@ export default definePlugin({
|
|||
replacement: [
|
||||
{
|
||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
||||
replace: (
|
||||
_,
|
||||
sectionTypes,
|
||||
commaOrSemi,
|
||||
elements,
|
||||
element,
|
||||
) =>
|
||||
replace: (_, sectionTypes, commaOrSemi, elements, element) =>
|
||||
`${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`,
|
||||
},
|
||||
{
|
||||
|
|
@ -146,12 +155,10 @@ export default definePlugin({
|
|||
},
|
||||
],
|
||||
},
|
||||
// Fix the settings cog context menu to work properly
|
||||
{
|
||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
||||
replacement: {
|
||||
// Skip the check Discord performs to make sure the section being selected in the user settings context menu is valid
|
||||
match: /(?<=function\((\i),(\i),\i\)\{)(?=let \i=Object.values\(\i\.\i\).+?(\(0,\i\.openUserSettings\))\()/,
|
||||
match: /(?<=function\((\i),(\i),\i\)\{)(?=let \i=Object\.values\(\i\.\i\).+?(\(0,\i\.openUserSettings\))\()/,
|
||||
replace: (_, settingsPanel, section, openUserSettings) => `${openUserSettings}(${settingsPanel},{section:${section}});return;`
|
||||
}
|
||||
},
|
||||
|
|
@ -178,40 +185,20 @@ export default definePlugin({
|
|||
key: key + "_panel",
|
||||
type: LayoutTypes.PANEL,
|
||||
useTitle: () => panelTitle,
|
||||
};
|
||||
|
||||
const render = {
|
||||
// FIXME
|
||||
buildLayout: () => [],
|
||||
StronglyDiscouragedCustomComponent: () => <Component />,
|
||||
render: () => <Component />,
|
||||
};
|
||||
|
||||
// FIXME
|
||||
if (LayoutTypes.PANE) {
|
||||
panel.buildLayout = () => [
|
||||
{
|
||||
key: key + "_pane",
|
||||
type: LayoutTypes.PANE,
|
||||
useTitle: () => panelTitle,
|
||||
buildLayout: () => [],
|
||||
...render
|
||||
}
|
||||
];
|
||||
} else {
|
||||
Object.assign(panel, render);
|
||||
panel.buildLayout = () => [];
|
||||
}
|
||||
|
||||
return ({
|
||||
return {
|
||||
key,
|
||||
type: LayoutTypes.SIDEBAR_ITEM,
|
||||
// FIXME
|
||||
legacySearchKey: title.toUpperCase(),
|
||||
getLegacySearchKey: () => title.toUpperCase(),
|
||||
useTitle: () => title,
|
||||
icon: () => <Icon width={20} height={20} />,
|
||||
buildLayout: () => [panel]
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
getSettingsSectionMappings() {
|
||||
|
|
@ -235,7 +222,6 @@ export default definePlugin({
|
|||
const layout = originalLayoutBuilder.buildLayout();
|
||||
if (originalLayoutBuilder.key !== "$Root") return layout;
|
||||
if (!Array.isArray(layout)) return layout;
|
||||
|
||||
if (layout.some(s => s?.key === "equicord_section")) return layout;
|
||||
|
||||
const { buildEntry } = this;
|
||||
|
|
@ -292,19 +278,7 @@ export default definePlugin({
|
|||
Component: PatchHelperTab,
|
||||
Icon: PatchHelperIcon
|
||||
}),
|
||||
...this.customEntries.map(buildEntry),
|
||||
// TODO: Remove deprecated customSections in a future update
|
||||
...this.customSections.map((func, i) => {
|
||||
const { section, element, label } = func(FallbackSectionTypes);
|
||||
if (section !== "Equibop") return null;
|
||||
|
||||
return buildEntry({
|
||||
key: `equicord_deprecated_custom_${section}`,
|
||||
title: label,
|
||||
Component: element,
|
||||
Icon: VesktopSettingsIcon
|
||||
});
|
||||
})
|
||||
...this.customEntries.map(buildEntry)
|
||||
].filter(isTruthy);
|
||||
|
||||
const equicordSection: SettingsLayoutNode = {
|
||||
|
|
@ -339,11 +313,10 @@ export default definePlugin({
|
|||
return layout;
|
||||
},
|
||||
|
||||
/** @deprecated Use customEntries */
|
||||
customSections: [] as ((SectionTypes: SectionTypes) => any)[],
|
||||
customSections: [] as ((SectionTypes: Record<string, string>) => { section: string; element: ComponentType; label: string; id?: string; })[],
|
||||
customEntries: [] as EntryOptions[],
|
||||
|
||||
makeSettingsCategories(SectionTypes: SectionTypes) {
|
||||
makeSettingsCategories(SectionTypes: Record<string, string>) {
|
||||
return [
|
||||
{
|
||||
section: SectionTypes.HEADER,
|
||||
|
|
@ -414,15 +387,12 @@ export default definePlugin({
|
|||
|
||||
isRightSpot({ header, settings: s }: { header?: string; settings?: string[]; }) {
|
||||
const firstChild = s?.[0];
|
||||
// lowest two elements... sanity backup
|
||||
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS")
|
||||
return true;
|
||||
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
|
||||
|
||||
const { settingsLocation } = settings.store;
|
||||
|
||||
if (settingsLocation === "bottom") return firstChild === "LOGOUT";
|
||||
if (settingsLocation === "belowActivity")
|
||||
return firstChild === "CHANGELOG";
|
||||
if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG";
|
||||
|
||||
if (!header) return;
|
||||
|
||||
|
|
@ -434,10 +404,7 @@ export default definePlugin({
|
|||
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS"),
|
||||
};
|
||||
|
||||
if (
|
||||
!names[settingsLocation] ||
|
||||
names[settingsLocation].endsWith("_SETTINGS")
|
||||
)
|
||||
if (!names[settingsLocation] || names[settingsLocation].endsWith("_SETTINGS"))
|
||||
return firstChild === "PREMIUM";
|
||||
|
||||
return header === names[settingsLocation];
|
||||
|
|
@ -451,49 +418,39 @@ export default definePlugin({
|
|||
addSettings(
|
||||
elements: any[],
|
||||
element: { header?: string; settings: string[]; },
|
||||
sectionTypes: SectionTypes,
|
||||
SectionTypes: Record<string, string>,
|
||||
) {
|
||||
if (this.patchedSettings.has(elements) || !this.isRightSpot(element))
|
||||
return;
|
||||
if (this.patchedSettings.has(elements) || !this.isRightSpot(element)) return;
|
||||
|
||||
this.patchedSettings.add(elements);
|
||||
|
||||
elements.push(...this.makeSettingsCategories(sectionTypes));
|
||||
elements.push(...this.makeSettingsCategories(SectionTypes));
|
||||
},
|
||||
|
||||
wrapSettingsHook(
|
||||
originalHook: (...args: any[]) => Record<string, unknown>[],
|
||||
) {
|
||||
wrapSettingsHook(originalHook: (...args: any[]) => Record<string, unknown>[]) {
|
||||
return (...args: any[]) => {
|
||||
const elements = originalHook(...args);
|
||||
if (!this.patchedSettings.has(elements))
|
||||
elements.unshift(...this.makeSettingsCategories(FallbackSectionTypes));
|
||||
elements.unshift(...this.makeSettingsCategories({ HEADER: SectionType.HEADER, DIVIDER: SectionType.DIVIDER, CUSTOM: SectionType.CUSTOM }) as Record<string, unknown>[]);
|
||||
|
||||
return elements;
|
||||
};
|
||||
},
|
||||
|
||||
get electronVersion() {
|
||||
return (
|
||||
VencordNative.native.getVersions().electron ||
|
||||
window.legcord?.electron ||
|
||||
null
|
||||
);
|
||||
return VencordNative.native.getVersions().electron ?? window.legcord?.electron ?? null;
|
||||
},
|
||||
|
||||
get chromiumVersion() {
|
||||
try {
|
||||
return (
|
||||
VencordNative.native.getVersions().chrome ||
|
||||
// @ts-expect-error Typescript will add userAgentData IMMEDIATELY
|
||||
VencordNative.native.getVersions().chrome ??
|
||||
// @ts-expect-error userAgentData types
|
||||
navigator.userAgentData?.brands?.find(
|
||||
b =>
|
||||
b.brand === "Chromium" || b.brand === "Google Chrome",
|
||||
)?.version ||
|
||||
(b: { brand: string; }) => b.brand === "Chromium" || b.brand === "Google Chrome",
|
||||
)?.version ??
|
||||
null
|
||||
);
|
||||
} catch {
|
||||
// inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue