diff --git a/src/components/ExpandableCard.css b/src/components/ExpandableCard.css new file mode 100644 index 000000000..54ad0f712 --- /dev/null +++ b/src/components/ExpandableCard.css @@ -0,0 +1,23 @@ +.vc-expandable-card { + color: var(--text-default); +} + +.vc-expandable-card-header { + display: flex; + cursor: pointer; + padding: .75em 1em; + margin: 4px; + border-radius: var(--radius-xs); + + .vc-expandable-card[data-expanded="true"] & { + background: var(--card-secondary-bg); + } +} + +.vc-expandable-card-icon { + margin-left: auto; +} + +.vc-expandable-card-content { + padding: 0.5em 1em; +} \ No newline at end of file diff --git a/src/components/ExpandableCard.tsx b/src/components/ExpandableCard.tsx new file mode 100644 index 000000000..31ee11a24 --- /dev/null +++ b/src/components/ExpandableCard.tsx @@ -0,0 +1,45 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./ExpandableCard.css"; + +import { classes } from "@utils/misc"; +import { Clickable, useState } from "@webpack/common"; +import { PropsWithChildren } from "react"; + +import { Card } from "./Card"; +import { DownArrow, RightArrow } from "./Icons"; + +export type ExpandableSectionProps = PropsWithChildren<{ + renderContent: () => React.ReactNode; + className?: string; + initialExpanded?: boolean; +}>; + +/** + * A card component that can expand and collapse to show/hide content. The header (props.children) is always visible, and the content (props.renderContent) is only visible when expanded. + */ +export function ExpandableSection({ children, renderContent: Content, className, initialExpanded = false }: ExpandableSectionProps) { + const [expanded, setExpanded] = useState(initialExpanded); + + const Icon = expanded ? DownArrow : RightArrow; + + return ( + + setExpanded(c => !c)} > + {children} + + + + {expanded + ?
+ +
+ : null + } +
+ ); +} diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 835ab7373..16f1e2bcf 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -714,7 +714,7 @@ export function CloudUploadIcon(props: IconProps) { ); } -export const ClockIcon = (props?: any) => { +export function ClockIcon(props: IconProps) { return ( { /> ); -}; +} + +export function DownArrow(props: IconProps) { + return ( + + + + ); +} + +export function RightArrow(props: IconProps) { + return ( + + + + ); +} export const QrCodeIcon = (props?: any) => { return ( diff --git a/src/plugins/_api/messageDecorations/index.tsx b/src/plugins/_api/messageDecorations/index.tsx index 6d6dec25d..c21e691d1 100644 --- a/src/plugins/_api/messageDecorations/index.tsx +++ b/src/plugins/_api/messageDecorations/index.tsx @@ -30,7 +30,7 @@ export default definePlugin({ patches: [ { - find: '"role-icon-children"', + find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", replacement: { match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?renderPopout:.+?(?=\])/, replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" diff --git a/src/plugins/customCommands/CreateTagModal.tsx b/src/plugins/customCommands/CreateTagModal.tsx new file mode 100644 index 000000000..652f18503 --- /dev/null +++ b/src/plugins/customCommands/CreateTagModal.tsx @@ -0,0 +1,133 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { BaseText } from "@components/BaseText"; +import { Button } from "@components/Button"; +import { Card } from "@components/Card"; +import { InlineCode } from "@components/CodeBlock"; +import { ExpandableSection } from "@components/ExpandableCard"; +import { Flex } from "@components/Flex"; +import { HeadingSecondary } from "@components/Heading"; +import { InfoIcon } from "@components/Icons"; +import { Margins } from "@components/margins"; +import { Paragraph } from "@components/Paragraph"; +import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { TextArea, TextInput, useState } from "@webpack/common"; + +import { parseTagArguments } from "."; +import { addTag, getTag, Tag } from "./settings"; + +export function openCreateTagModal(initialValue: Tag = { name: "", message: "" }) { + openModal(modalProps => ( + + )); +} + +const EXAMPLE_RESPONSE = "Hello {{user}}! I am feeling {{mood = great}}."; + +function Modal({ initialValue, modalProps }: { initialValue: Tag; modalProps: ModalProps; }) { + const [name, setName] = useState(initialValue.name); + const [message, setMessage] = useState(initialValue.message.replaceAll("\\n", "\n")); + + const detectedArguments = parseTagArguments(message); + const hasReservedEphemeral = detectedArguments.some(arg => arg.name === "ephemeral"); + const nameAlreadyExists = name !== initialValue.name && getTag(name); + + return ( + + + Create Tag + + + + + + Create a new tag which will be registered as a slash command. + +
+ Name + +
+ +
+ Response +