diff --git a/src/components/Card.css b/src/components/Card.css new file mode 100644 index 000000000..8d4e74a98 --- /dev/null +++ b/src/components/Card.css @@ -0,0 +1,20 @@ +.vc-card-base { + background: var(--card-primary-bg); + border-radius: var(--radius-sm, 8px) +} + +.vc-card-normal { + border: 1px solid var(--border-subtle); +} + +.vc-card-warning { + border: 1px solid var(--info-warning-foreground); +} + +.vc-card-danger { + border: 1px solid var(--info-danger-foreground); +} + +.vc-card-defaultPadding { + padding: 1em; +} \ No newline at end of file diff --git a/src/components/Card.tsx b/src/components/Card.tsx new file mode 100644 index 000000000..be32d978d --- /dev/null +++ b/src/components/Card.tsx @@ -0,0 +1,31 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./Card.css"; + +import { classNameFactory } from "@api/Styles"; +import { classes } from "@utils/misc"; +import { ComponentPropsWithRef } from "react"; + +const cl = classNameFactory("vc-card-"); + +export interface CardProps extends ComponentPropsWithRef<"div"> { + variant?: "normal" | "warning" | "danger"; + /** Add a default padding of 1em to the card. This is implied if no className prop is passed */ + defaultPadding?: boolean; +} + +export function Card({ variant = "normal", defaultPadding, children, className, ...restProps }: CardProps) { + const addDefaultPadding = defaultPadding != null + ? defaultPadding + : !className; + + return ( +
+ {children} +
+ ); +} diff --git a/src/components/Flex.tsx b/src/components/Flex.tsx index 430d85768..065ddac74 100644 --- a/src/components/Flex.tsx +++ b/src/components/Flex.tsx @@ -16,22 +16,28 @@ * along with this program. If not, see . */ -import type { React } from "@webpack/common"; +import type { CSSProperties, HTMLAttributes } from "react"; + +export interface FlexProps extends HTMLAttributes { + flexDirection?: CSSProperties["flexDirection"]; + gap?: CSSProperties["gap"]; + justifyContent?: CSSProperties["justifyContent"]; + alignItems?: CSSProperties["alignItems"]; +} + +export function Flex({ flexDirection, gap = "1em", justifyContent, alignItems, children, style, ...restProps }: FlexProps) { + style ??= {}; + Object.assign(style, { + display: "flex", + flexDirection, + gap, + justifyContent, + alignItems, + }); -export function Flex(props: React.PropsWithChildren<{ - flexDirection?: React.CSSProperties["flexDirection"]; - style?: React.CSSProperties; - className?: string; -} & React.HTMLProps>) { - props.style ??= {}; - props.style.display = "flex"; - // TODO(ven): Remove me, what was I thinking?? - props.style.gap ??= "1em"; - props.style.flexDirection ||= props.flexDirection; - delete props.flexDirection; return ( -
- {props.children} +
+ {children}
); } diff --git a/src/components/index.ts b/src/components/index.ts index cf177f28b..dd851be5d 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -5,6 +5,8 @@ */ export * from "./BaseText"; +export * from "./Button"; +export * from "./Card"; export * from "./CheckedTextInput"; export * from "./CodeBlock"; export * from "./Divider"; diff --git a/src/components/settings/QuickAction.tsx b/src/components/settings/QuickAction.tsx index d1a8f52c4..f4acbb69f 100644 --- a/src/components/settings/QuickAction.tsx +++ b/src/components/settings/QuickAction.tsx @@ -7,7 +7,7 @@ import "./QuickAction.css"; import { classNameFactory } from "@api/Styles"; -import { Card } from "@webpack/common"; +import { Card } from "@components/Card"; import type { ComponentType, PropsWithChildren, ReactNode } from "react"; const cl = classNameFactory("vc-settings-quickActions-"); diff --git a/src/components/settings/SpecialCard.tsx b/src/components/settings/SpecialCard.tsx index 9f66e01b2..16c3cac05 100644 --- a/src/components/settings/SpecialCard.tsx +++ b/src/components/settings/SpecialCard.tsx @@ -19,8 +19,9 @@ import "./SpecialCard.css"; import { classNameFactory } from "@api/Styles"; +import { Card } from "@components/Card"; import { Divider } from "@components/Divider"; -import { Card, Clickable, Forms, React } from "@webpack/common"; +import { Clickable, Forms } from "@webpack/common"; import type { PropsWithChildren } from "react"; const cl = classNameFactory("vc-special-"); diff --git a/src/components/settings/tabs/plugins/index.tsx b/src/components/settings/tabs/plugins/index.tsx index cb8776ae3..56cfe582f 100644 --- a/src/components/settings/tabs/plugins/index.tsx +++ b/src/components/settings/tabs/plugins/index.tsx @@ -22,6 +22,7 @@ import * as DataStore from "@api/DataStore"; import { isPluginEnabled } from "@api/PluginManager"; import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { Card } from "@components/Card"; import { Divider } from "@components/Divider"; import ErrorBoundary from "@components/ErrorBoundary"; import { HeadingTertiary } from "@components/Heading"; @@ -32,7 +33,7 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { useAwaiter, useCleanupEffect } from "@utils/react"; -import { Alerts, Button, Card, lodash, Parser, React, Select, TextInput, Tooltip, useMemo, useState } from "@webpack/common"; +import { Alerts, Button, lodash, Parser, React, Select, TextInput, Tooltip, useMemo, useState } from "@webpack/common"; import { JSX } from "react"; import Plugins, { ExcludedPlugins } from "~plugins"; @@ -44,7 +45,7 @@ export const logger = new Logger("PluginSettings", "#a6d189"); function ReloadRequiredCard({ required }: { required: boolean; }) { return ( - + {required ? ( <> diff --git a/src/components/settings/tabs/styles.css b/src/components/settings/tabs/styles.css index 2ff8cb9f0..f2828b31c 100644 --- a/src/components/settings/tabs/styles.css +++ b/src/components/settings/tabs/styles.css @@ -15,24 +15,6 @@ flex-direction: row; } -.vc-settings-card { - padding: 1em; - margin-bottom: 1em; -} - -.vc-warning-card { - padding: 1em; - background: var(--info-warning-background); - border: 1px solid var(--info-warning-foreground); - color: var(--info-warning-foreground); -} - -.vc-backup-restore-card { - background-color: var(--info-warning-background); - border-color: var(--info-warning-foreground); - color: var(--info-warning-foreground); -} - .vc-settings-theme-links { /* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */ display: inline-block !important; diff --git a/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx index 2356c4c26..e4e8b001f 100644 --- a/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx +++ b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx @@ -17,49 +17,48 @@ */ import { downloadSettingsBackup, uploadSettingsBackup } from "@api/SettingsSync/offline"; +import { Card } from "@components/Card"; import { Flex } from "@components/Flex"; +import { Heading } from "@components/Heading"; +import { Paragraph } from "@components/Paragraph"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; -import { Button, Card, Text } from "@webpack/common"; +import { Button, Text } from "@webpack/common"; function BackupAndRestoreTab() { return ( - - - Warning - Importing a settings file will overwrite your current settings. + + + Warning + Importing a settings file will overwrite your current settings. + + + + You can import and export your Vencord settings as a JSON file. + This allows you to easily transfer your settings to another device, + or recover your settings after reinstalling Vencord or Discord. + + + Settings Export contains: + +
    +
  • — Custom QuickCSS
  • +
  • — Theme Links
  • +
  • — Plugin Settings
  • +
+
+ + + + -
- - You can import and export your Vencord settings as a JSON file. - This allows you to easily transfer your settings to another device, - or recover your settings after reinstalling Vencord or Discord. - - - Settings Export contains: -
    -
  • — Custom QuickCSS
  • -
  • — Theme Links
  • -
  • — Plugin Settings
  • -
-
- - - -
+ ); } diff --git a/src/components/settings/tabs/themes/CspErrorCard.tsx b/src/components/settings/tabs/themes/CspErrorCard.tsx index f6486374d..685b9a91a 100644 --- a/src/components/settings/tabs/themes/CspErrorCard.tsx +++ b/src/components/settings/tabs/themes/CspErrorCard.tsx @@ -50,7 +50,7 @@ export function CspErrorCard() { const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain); return ( - + Blocked Resources Some images, styles, or fonts were blocked because they come from disallowed domains. It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them. diff --git a/src/components/settings/tabs/themes/LocalThemesTab.tsx b/src/components/settings/tabs/themes/LocalThemesTab.tsx index cea4d7306..02554b680 100644 --- a/src/components/settings/tabs/themes/LocalThemesTab.tsx +++ b/src/components/settings/tabs/themes/LocalThemesTab.tsx @@ -7,6 +7,8 @@ import { isPluginEnabled } from "@api/PluginManager"; import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { Card } from "@components/Card"; +import { Flex } from "@components/Flex"; import { FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons"; import { Link } from "@components/Link"; import { QuickAction, QuickActionCard } from "@components/settings/QuickAction"; @@ -14,7 +16,7 @@ import { openPluginModal } from "@components/settings/tabs/plugins/PluginModal"; import { UserThemeHeader } from "@main/themes"; import ClientThemePlugin from "@plugins/clientTheme"; import { findLazy } from "@webpack"; -import { Card, Forms, useEffect, useRef, useState } from "@webpack/common"; +import { Forms, useEffect, useRef, useState } from "@webpack/common"; import type { ComponentType, Ref, SyntheticEvent } from "react"; import { ThemeCard } from "./ThemeCard"; @@ -82,8 +84,8 @@ export function LocalThemesTab() { } return ( - <> - + + Find Themes:
@@ -94,7 +96,7 @@ export function LocalThemesTab() { If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder. - + External Resources For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked. Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts. @@ -167,6 +169,6 @@ export function LocalThemesTab() { ))}
- +
); } diff --git a/src/components/settings/tabs/themes/OnlineThemesTab.tsx b/src/components/settings/tabs/themes/OnlineThemesTab.tsx index 7db955fd9..c314b872a 100644 --- a/src/components/settings/tabs/themes/OnlineThemesTab.tsx +++ b/src/components/settings/tabs/themes/OnlineThemesTab.tsx @@ -5,9 +5,9 @@ */ import { useSettings } from "@api/Settings"; -import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; -import { Card, Forms, TextArea, useState } from "@webpack/common"; +import { Card } from "@components/Card"; +import { Flex } from "@components/Flex"; +import { Forms, TextArea, useState } from "@webpack/common"; export function OnlineThemesTab() { const settings = useSettings(["themeLinks"]); @@ -26,14 +26,14 @@ export function OnlineThemesTab() { } return ( - <> - + + This section is for advanced users. If you are having difficulties using it, use the Local Themes tab instead. - + Paste links to css files here One link per line You can prefix lines with @light or @dark to toggle them based on your Discord theme @@ -52,6 +52,6 @@ export function OnlineThemesTab() { rows={10} /> - + ); } diff --git a/src/components/settings/tabs/themes/index.tsx b/src/components/settings/tabs/themes/index.tsx index 40653a4c3..7d2234459 100644 --- a/src/components/settings/tabs/themes/index.tsx +++ b/src/components/settings/tabs/themes/index.tsx @@ -18,10 +18,11 @@ import "./styles.css"; +import { Card } from "@components/Card"; import { Link } from "@components/Link"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { getStylusWebStoreUrl } from "@utils/web"; -import { Card, Forms, React, TabBar, useState } from "@webpack/common"; +import { Forms, React, TabBar, useState } from "@webpack/common"; import { CspErrorCard } from "./CspErrorCard"; import { LocalThemesTab } from "./LocalThemesTab"; @@ -69,7 +70,7 @@ function ThemesTab() { function UserscriptThemesTab() { return ( - + Themes are not supported on the Userscript! diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx index adc7e1908..f30587d59 100644 --- a/src/components/settings/tabs/updater/Components.tsx +++ b/src/components/settings/tabs/updater/Components.tsx @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { Card } from "@components/Card"; import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; @@ -11,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; import { changes, checkForUpdates, update, updateError } from "@utils/updater"; -import { Alerts, Button, Card, Forms, React, Toasts, useState } from "@webpack/common"; +import { Alerts, Button, Forms, React, Toasts, useState } from "@webpack/common"; import { runWithDispatch } from "./runWithDispatch"; @@ -30,7 +31,7 @@ export function HashLink({ repo, hash, disabled = false }: { repo: string, hash: export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { return ( - + {updates.map(({ hash, author, message }) => (
- Updater Settings + Updater Settings + Please do not private message Vencord plugin developers for support!
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")} diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index e6de5df03..76826d9d8 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -22,6 +22,7 @@ import "./styles.css"; import { definePluginSettings } from "@api/Settings"; import { Divider } from "@components/Divider"; import ErrorBoundary from "@components/ErrorBoundary"; +import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import { copyWithToast, fetchUserProfile } from "@utils/discord"; import { Margins } from "@utils/margins"; @@ -30,7 +31,7 @@ import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; import { User, UserProfile } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; -import { Button, ColorPicker, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; +import { Button, ColorPicker, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; import virtualMerge from "virtual-merge"; interface Colors { @@ -119,23 +120,19 @@ function SettingsAboutComponent() { After enabling this plugin, you will see custom colors in the profiles of other people using compatible plugins.{" "} -
- To set your own colors: +
+ + To set your own profile theme colors:
    -
  • - • use the color pickers below to choose your colors -
  • -
  • • click the "Copy 3y3" button
  • -
  • • paste the invisible text anywhere in your bio
  • -

+
  • — use the color pickers below to choose your colors
  • +
  • — click the "Copy 3y3" button
  • +
  • — paste the invisible text anywhere in your bio
  • + Color pickers - + Copy 3y3 diff --git a/src/plugins/replaceGoogleSearch/index.tsx b/src/plugins/replaceGoogleSearch/index.tsx index bfa9b1768..515db54ff 100644 --- a/src/plugins/replaceGoogleSearch/index.tsx +++ b/src/plugins/replaceGoogleSearch/index.tsx @@ -6,9 +6,10 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; +import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Flex, Menu } from "@webpack/common"; +import { Menu } from "@webpack/common"; const DefaultEngines = { Google: "https://www.google.com/search?q=", @@ -93,7 +94,7 @@ function makeSearchItem(src: string) { key={key} id={key} label={ - + - + diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 68751a0fe..4210d89ca 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -19,6 +19,7 @@ import "./styles.css"; import { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { Card } from "@components/Card"; import { Microphone } from "@components/Icons"; import { Link } from "@components/Link"; import { Paragraph } from "@components/Paragraph"; @@ -31,7 +32,7 @@ import { chooseFile } from "@utils/web"; import { CloudUpload as TCloudUpload } from "@vencord/discord-types"; import { CloudUploadPlatform } from "@vencord/discord-types/enums"; import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; -import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; +import { Button, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { ComponentType } from "react"; import { VoiceRecorderDesktop } from "./DesktopRecorder"; @@ -226,7 +227,7 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) { )} {isUnsupportedFormat && ( - + Voice Messages have to be OggOpus to be playable on iOS. This file is {blob.type} so it will not be playable on iOS. diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index cc282fd91..659cd8dfd 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -30,7 +30,9 @@ import { waitForComponent } from "./internal"; export const Forms = { // TODO: Stop using this and use Heading/Paragraph directly + /** @deprecated use Heading from Vencord */ FormTitle: Heading, + /** @deprecated use Paragraph from Vencord */ FormText: Paragraph, /** @deprecated don't use this */ FormSection: "section" as never, // Backwards compat since Vesktop uses this @@ -39,12 +41,15 @@ export const Forms = { }; // TODO: Stop using this and use Paragraph/Span directly +/** @deprecated use Paragraph, Span, or BaseText from Vencord */ export const Text = TextCompat; +/** @deprecated use Button from Vencord */ export const Button = ButtonCompat; /** @deprecated Use FormSwitch from Vencord */ export const Switch = FormSwitchCompat as never; -export const Card = waitForComponent("Card", filters.componentByCode(".editable),", ".outline:")); +/** @deprecated Use Card from Vencord */ +export const Card = waitForComponent("Card", filters.componentByCode(".editable),", ".outline:")); export const Checkbox = waitForComponent("Checkbox", filters.componentByCode(".checkboxWrapperDisabled:")); const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", { @@ -55,6 +60,7 @@ const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", { TooltipContainer: t.TooltipContainer; }; +// TODO: if these finds break, they should just return their children export const Tooltip = LazyComponent(() => Tooltips.Tooltip); export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer); @@ -67,6 +73,7 @@ export const Popout = waitForComponent("Popout", filters.componentByCo export const Dialog = waitForComponent("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1')); export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:")); export const Paginator = waitForComponent("Paginator", filters.componentByCode('rel:"prev",children:')); +// TODO: remake this component export const Clickable = waitForComponent("Clickable", filters.componentByCode("this.context?this.renderNonInteractive():")); export const Avatar = waitForComponent("Avatar", filters.componentByCode(".size-1.375*")); @@ -106,7 +113,6 @@ waitFor(m => { export const MaskedLink = waitForComponent("MaskedLink", filters.componentByCode("MASKED_LINK)")); export const Timestamp = waitForComponent("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}")); -export const Flex = waitForComponent("Flex", ["Justify", "Align", "Wrap"]); export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode(".authorize,children:", ".contentBackground")); export const Animations = mapMangledModuleLazy(".assign({colorNames:", {