diff --git a/plugins/better-timestamps/src/ui/Settings.tsx b/plugins/better-timestamps/src/ui/Settings.tsx
index aee8330..e784202 100644
--- a/plugins/better-timestamps/src/ui/Settings.tsx
+++ b/plugins/better-timestamps/src/ui/Settings.tsx
@@ -1,13 +1,12 @@
import { React, ReactNative, moment } from "@vendetta/metro/common";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
-import { Forms } from "@vendetta/ui/components";
+import { TableRow, TableRowGroup, TableSwitchRow, TextInput } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import { resolveSemanticColorSafe } from "@shared/lib/color";
import renderTimestamp, { TimestampMode } from "../lib/renderTimestamp";
const { Text } = ReactNative;
-const { FormSection, FormRow, FormDivider, FormSwitchRow, FormInput } = Forms;
const MODES: { label: string; key: TimestampMode }[] = [
{ label: "Calendar", key: "calendar" },
@@ -21,41 +20,40 @@ export default function Settings() {
return (
-
- {MODES.map((mode, i) => {
+
+ {MODES.map((mode) => {
const selected = storage.selected === mode.key;
let preview = "";
try { preview = renderTimestamp(moment(), mode.key); } catch { preview = ""; }
return (
- ✓ : undefined}
onPress={() => { storage.selected = mode.key; }}
/>
{mode.key === "custom" && selected && (
- { storage.customFormat = v; }}
/>
)}
- {i !== MODES.length - 1 && }
);
})}
-
-
-
+
+ { storage.separateMessages = v; }}
/>
-
+
);
}
diff --git a/plugins/custom-user-tags/src/ui/Settings.tsx b/plugins/custom-user-tags/src/ui/Settings.tsx
index 5ea176b..e7f280d 100644
--- a/plugins/custom-user-tags/src/ui/Settings.tsx
+++ b/plugins/custom-user-tags/src/ui/Settings.tsx
@@ -1,7 +1,7 @@
import { React, ReactNative } from "@vendetta/metro/common";
import { useProxy } from "@vendetta/storage";
import { storage } from "@vendetta/plugin";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TextInput } from "@shared/ui/table";
import { showToast } from "@vendetta/ui/toasts";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import ListSection from "@shared/ui/ListSection";
@@ -11,7 +11,6 @@ import NoteBox from "@shared/ui/NoteBox";
import { allTags, setUserTag, removeUserTag } from "../lib/tags";
const { View } = ReactNative;
-const { FormSection, FormInput } = Forms;
function AddTagForm() {
const [userId, setUserId] = React.useState("");
@@ -21,13 +20,13 @@ function AddTagForm() {
return (
-
-
+
-
+
-
+
-
+
}
diff --git a/plugins/fake-profile-themes-and-effects/src/ui/components/Builder.tsx b/plugins/fake-profile-themes-and-effects/src/ui/components/Builder.tsx
index c57c5e3..d1b1f78 100644
--- a/plugins/fake-profile-themes-and-effects/src/ui/components/Builder.tsx
+++ b/plugins/fake-profile-themes-and-effects/src/ui/components/Builder.tsx
@@ -1,14 +1,15 @@
import { showToast } from "@vendetta/ui/toasts";
import { findByProps } from "@vendetta/metro";
import React, { useMemo, useState, useEffect } from "react";
-import { View, Text } from "react-native";
+import { View } from "react-native";
import { buildFPTE, hasFPTE, stripFPTE } from "@fpte/lib/fpte";
import { type ProfileEffectConfig, UserStore, UserProfileStore } from "@fpte/lib/stores";
import { useAccentColor, usePrimaryColor, useShowPreview } from "@fpte/patches/patchUseProfileTheme";
import { showColorPicker, showEffectPicker } from "@fpte/ui/actionSheets";
import { resolveSemanticColorSafe, useAvatarColors, useThemeContext } from "@fpte/ui/color";
import { BuilderButton, Button, StaticEffect } from "@fpte/ui/components";
-import { FormCardSection, FormSwitchRow } from "@fpte/ui/components/forms";
+import { FormSwitchRow } from "@fpte/ui/components/forms";
+import { TableRowGroup } from "@shared/ui/table";
const UserProfileActionCreators = findByProps("saveProfileChanges");
@@ -103,23 +104,7 @@ export function Builder({ guildId }: BuilderProps) {
const applyButtonVisible = hasSelection || fpteActive;
return (
-
- FPTE Builder
-
- {fpteActive ? "Active" : "Inactive"}
-
-
- }
- cardStyle={{ backgroundColor: "transparent" }}
- >
+
*/}
-
+
);
}
\ No newline at end of file
diff --git a/plugins/fake-profile-themes-and-effects/src/ui/components/forms.ts b/plugins/fake-profile-themes-and-effects/src/ui/components/forms.ts
index ded3c69..e2ece5d 100644
--- a/plugins/fake-profile-themes-and-effects/src/ui/components/forms.ts
+++ b/plugins/fake-profile-themes-and-effects/src/ui/components/forms.ts
@@ -1,4 +1,4 @@
-import { Forms } from "@vendetta/ui/components";
+import { TableRow, TableRowGroup, TableSwitchRow } from "@shared/ui/table";
import type { ComponentType, ReactNode } from "react";
import type { ColorValue, PressableProps, ViewProps } from "react-native";
@@ -69,15 +69,6 @@ export interface FormRowProps extends Pick,
hasError?: boolean | undefined;
}
-export interface FormRadioProps {
- selected?: boolean | undefined;
-}
-
-export interface FormRadioRowProps extends FormRadioProps, FormRowProps {
- align?: "left" | "right";
- value?: boolean | undefined;
-}
-
export interface FormSwitchProps extends Pick {
value?: boolean | undefined;
onValueChange?: ((value: boolean) => void) | undefined;
@@ -90,19 +81,8 @@ export interface FormSwitchRowProps extends Pick {
- title?: ReactNode;
- headerComponent?: ReactNode;
- titleStyle?: TextProps["style"];
- cardStyle?: ViewProps["style"];
-}
+export const FormSection: ComponentType = TableRowGroup;
-export const FormSection: ComponentType = Forms.FormSection;
+export const FormRow: ComponentType = TableRow;
-export const FormRow: ComponentType = Forms.FormRow;
-
-export const FormRadioRow: ComponentType = Forms.FormRadioRow;
-
-export const FormSwitchRow: ComponentType = Forms.FormSwitchRow;
-
-export const FormCardSection: ComponentType = Forms.FormCardSection;
+export const FormSwitchRow: ComponentType = TableSwitchRow;
diff --git a/plugins/fake-profile-themes-and-effects/src/ui/pages/Settings.tsx b/plugins/fake-profile-themes-and-effects/src/ui/pages/Settings.tsx
index 1b0c5ff..62f1bfb 100644
--- a/plugins/fake-profile-themes-and-effects/src/ui/pages/Settings.tsx
+++ b/plugins/fake-profile-themes-and-effects/src/ui/pages/Settings.tsx
@@ -3,25 +3,23 @@ import { useProxy } from "@vendetta/storage";
import React from "react";
import { ScrollView } from "react-native";
-import { FormRadioRow, FormRow, FormSection, FormSwitchRow } from "@fpte/ui/components/forms";
+import { FormSwitchRow } from "@fpte/ui/components/forms";
+import { TableRadioGroup, TableRadioRow, TableRowGroup } from "@shared/ui/table";
export function Settings() {
useProxy(storage);
return (
-
-
- { storage.prioritizeNitro = true; }}
- />
- { storage.prioritizeNitro = false; }}
- />
+
+ { storage.prioritizeNitro = value === "nitro"; }}
+ >
+
+
+
{ storage.forceFallbackEffectPicker = value; }}
/>
-
+
);
}
diff --git a/plugins/key-inspector/src/ui/Settings.tsx b/plugins/key-inspector/src/ui/Settings.tsx
index 3c1791b..7d27c53 100644
--- a/plugins/key-inspector/src/ui/Settings.tsx
+++ b/plugins/key-inspector/src/ui/Settings.tsx
@@ -1,7 +1,7 @@
import { React, ReactNative, clipboard } from "@vendetta/metro/common";
import { findByProps, findByName, findByPropsAll, findByNameAll } from "@vendetta/metro";
import { showToast } from "@vendetta/ui/toasts";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TextInput } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import NoteBox from "@shared/ui/NoteBox";
import PrimaryButton from "@shared/ui/PrimaryButton";
@@ -14,7 +14,6 @@ import { captureFluxEvents } from "../lib/fluxLogger";
import { captureComponentRenders } from "../lib/renderLogger";
const { View, Text } = ReactNative;
-const { FormSection, FormInput } = Forms;
// Every raw Text below used to have no explicit color at all - illegible black-on-black on
// Discord's dark theme, since RN's Text defaults to black with no theming of its own.
@@ -24,7 +23,7 @@ function FullScanSection() {
const [summary, setSummary] = React.useState(null);
return (
-
+
{summary}
)}
-
+
);
}
@@ -63,10 +62,10 @@ function EvalSection() {
};
return (
-
+
-
-
+
);
}
@@ -125,16 +124,16 @@ function FluxLoggerSection() {
};
return (
-
+
-
-
-
+
);
}
@@ -186,16 +185,16 @@ function RenderLoggerSection() {
};
return (
-
+
-
-
-
+
+
-
+
);
}
@@ -242,10 +241,10 @@ function RawSearchSection() {
};
return (
-
+
-
-
+
nameQuery.trim() && run(`rawFindByName("${nameQuery.trim()}")`, () => rawFindByName(nameQuery.trim()))}
/>
-
+
typeQuery.trim() && run(`rawFindByTypeName("${typeQuery.trim()}")`, () => rawFindByTypeName(typeQuery.trim()))}
/>
-
+
-
+
);
}
@@ -362,7 +361,7 @@ function FiberCaptureSection() {
};
return (
-
+
{status}
@@ -375,13 +374,13 @@ function FiberCaptureSection() {
module-registry guessing) from window.__keyInspectorFiberRoot afterward.
-
+
);
}
function ApiTreeSection() {
return (
-
+
-
+
);
}
@@ -477,10 +476,10 @@ function ManualSearchSection() {
};
return (
-
+
-
-
-
+
);
}
diff --git a/plugins/message-snippets/src/ui/Settings.tsx b/plugins/message-snippets/src/ui/Settings.tsx
index f1da953..c18fdb8 100644
--- a/plugins/message-snippets/src/ui/Settings.tsx
+++ b/plugins/message-snippets/src/ui/Settings.tsx
@@ -1,7 +1,7 @@
import { React, ReactNative } from "@vendetta/metro/common";
import { useProxy } from "@vendetta/storage";
import { storage } from "@vendetta/plugin";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TextInput } from "@shared/ui/table";
import { showToast } from "@vendetta/ui/toasts";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import ListSection from "@shared/ui/ListSection";
@@ -9,7 +9,6 @@ import PrimaryButton from "@shared/ui/PrimaryButton";
import { getSnippets, saveSnippet, deleteSnippet } from "../lib/snippets";
const { View } = ReactNative;
-const { FormSection, FormInput } = Forms;
function NewSnippetForm() {
const [name, setName] = React.useState("");
@@ -18,8 +17,8 @@ function NewSnippetForm() {
return (
-
-
+
+
-
+
-
+
-
+
This plugin doesn't have anything to configure here - there's no API key or toggle,
because pronouns aren't Discord data. They come from PronounDB, a separate service
@@ -27,7 +26,7 @@ export default function Settings() {
Linking.openURL(PRONOUNDB_URL)} />
-
+
);
}
diff --git a/plugins/radial-status/src/ui/Settings.tsx b/plugins/radial-status/src/ui/Settings.tsx
index 8194679..337860a 100644
--- a/plugins/radial-status/src/ui/Settings.tsx
+++ b/plugins/radial-status/src/ui/Settings.tsx
@@ -1,13 +1,11 @@
import { React } from "@vendetta/metro/common";
import { useProxy } from "@vendetta/storage";
import { storage } from "@vendetta/plugin";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TextInput, TableSwitchRow } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import ColorInput from "@shared/ui/ColorInput";
import NoteBox from "@shared/ui/NoteBox";
-const { FormSection, FormInput, FormSwitchRow } = Forms;
-
// The ring patch itself does a generic colors[status] lookup, not one hardcoded to three statuses -
// offline just needed its own configurable entry here to work the same way the other three already
// do. Discord doesn't show a colored dot for offline at all normally, so this is opt-in (blank by
@@ -21,8 +19,8 @@ const STATUSES: { key: string; label: string; defaultColor?: string }[] = [
function numberInput(title: string, key: "ringThickness", placeholder: string) {
return (
- {
@@ -34,6 +32,7 @@ function numberInput(title: string, key: "ringThickness", placeholder: string) {
);
}
+
export default function Settings() {
useProxy(storage);
storage.colors ??= {};
@@ -47,14 +46,14 @@ export default function Settings() {
avatar sizes seen in each of those contexts, so other unrelated circular UI elements are
left alone.
-
-
+ { storage.enabled = v; }}
/>
-
-
+
+
{STATUSES.map(({ key, label, defaultColor }) => (
))}
-
-
+
+
{numberInput("Ring thickness", "ringThickness", "2")}
-
+
);
}
diff --git a/plugins/role-color-everywhere/src/ui/Settings.tsx b/plugins/role-color-everywhere/src/ui/Settings.tsx
index 92ca89c..6a3d7d9 100644
--- a/plugins/role-color-everywhere/src/ui/Settings.tsx
+++ b/plugins/role-color-everywhere/src/ui/Settings.tsx
@@ -1,50 +1,48 @@
import { React } from "@vendetta/metro/common";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TableSwitchRow, TextInput } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import NoteBox from "@shared/ui/NoteBox";
-const { FormSection, FormSwitchRow, FormInput } = Forms;
-
export default function Settings() {
useProxy(storage);
return (
-
-
+ { storage.hideTyping = !v; }}
/>
- { storage.noMention = !v; }}
/>
- { storage.noRole = !v; }}
/>
- { storage.noVoice = !v; }}
/>
- 0}
onValueChange={(v: boolean) => { storage.chatInterpolation = v ? 60 : 0; }}
/>
{storage.chatInterpolation > 0 && (
- {
@@ -52,7 +50,7 @@ export default function Settings() {
}}
/>
)}
-
+
Some of these surfaces (especially the member list headers) rely on component internals
Discord doesn't officially expose, so a future Discord update may silently turn one of
diff --git a/plugins/rose-utils/src/modules/MessageLogger/ui/LogViewerPage.tsx b/plugins/rose-utils/src/modules/MessageLogger/ui/LogViewerPage.tsx
index cf260d7..62257dd 100644
--- a/plugins/rose-utils/src/modules/MessageLogger/ui/LogViewerPage.tsx
+++ b/plugins/rose-utils/src/modules/MessageLogger/ui/LogViewerPage.tsx
@@ -1,6 +1,6 @@
import { findByName, findByProps } from "@vendetta/metro";
import { React, ReactNative, clipboard } from "@vendetta/metro/common";
-import { Forms } from "@vendetta/ui/components";
+import { TextInput } from "@shared/ui/table";
import { showToast } from "@vendetta/ui/toasts";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { resolveSemanticColorSafe } from "@shared/lib/color";
@@ -17,7 +17,6 @@ const modalCloseButton =
const Navigator = findByName("Navigator") ?? findByProps("Navigator")?.Navigator;
const { View, Text, ScrollView, TouchableOpacity } = ReactNative;
-const { FormInput } = Forms;
const PAGE_SIZE = 100;
@@ -172,8 +171,8 @@ function LogList() {
return (
-
-
+
))}
{errorEntries.length > 0 && (
- {
const report = errorEntries.map(([label, stack]) => `[${module.label}] ${label}\n${stack}`).join("\n\n");
@@ -136,7 +135,7 @@ function ModuleSection({ module }: { module: AnyModule }) {
}}
/>
)}
-
+
);
}
@@ -212,8 +211,8 @@ export default function Settings() {
-
-
-
+
- { storage.showUnreadBadges = v; }}
/>
- { storage.showGuildNames = v; }}
/>
- { storage.hideDmTile = v; }}
/>
-
-
-
+
+ { storage.autoCollapseFolders = v; }}
/>
- { storage.hideFolderIcons = v; }}
/>
-
+
The folder options above are ported from fres621's BetterFolders - its other half
(hiding icons in the native left server rail) patched a component that no longer
diff --git a/plugins/staff-tags/src/ui/pages/Settings.tsx b/plugins/staff-tags/src/ui/pages/Settings.tsx
index 99e72df..54cfad8 100644
--- a/plugins/staff-tags/src/ui/pages/Settings.tsx
+++ b/plugins/staff-tags/src/ui/pages/Settings.tsx
@@ -1,12 +1,11 @@
import { React } from "@vendetta/metro/common";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TableSwitchRow, TextInput } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import ColorInput from "@shared/ui/ColorInput";
import { TAG_DEFINITIONS, tagSettings } from "../../lib/getTag";
-const { FormSection, FormSwitchRow, FormInput } = Forms;
function TagSettingsSection({ id, defaultText, defaultColor }: { id: string; defaultText: string; defaultColor: string }) {
const settings = tagSettings(id);
@@ -15,20 +14,20 @@ function TagSettingsSection({ id, defaultText, defaultColor }: { id: string; def
const enabled = settings.enabled !== false;
return (
-
-
+ { settings.enabled = v; }}
/>
- { settings.text = v; }}
/>
- { settings.color = v; }}
/>
)}
- { settings.gradientColor = v; }}
/>
)}
-
+
);
}
@@ -66,14 +65,14 @@ export default function Settings() {
return (
-
-
+ { storage.useRoleColor = v; }}
/>
-
+
{TAG_DEFINITIONS.map((def) => (
))}
diff --git a/plugins/view-raw/src/ui/Settings.tsx b/plugins/view-raw/src/ui/Settings.tsx
index dd452bc..71c5082 100644
--- a/plugins/view-raw/src/ui/Settings.tsx
+++ b/plugins/view-raw/src/ui/Settings.tsx
@@ -1,5 +1,5 @@
import { React } from "@vendetta/metro/common";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TableRow } from "@shared/ui/table";
import { showToast } from "@vendetta/ui/toasts";
import { clipboard } from "@vendetta/metro/common";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
@@ -7,8 +7,6 @@ import NoteBox from "@shared/ui/NoteBox";
import PrimaryButton from "@shared/ui/PrimaryButton";
import { getLastDetection } from "../lib/diagnostics";
-const { FormSection, FormRow } = Forms;
-
const STRATEGY_LABEL: Record = {
buttons: "Strategy 1 (button list) - working normally",
actionSheetRow: "Strategy 2 (row group) - working normally",
@@ -21,9 +19,9 @@ export default function Settings() {
return (
-
+
{detection ? (
-
@@ -41,7 +39,7 @@ export default function Settings() {
showToast("Copied - paste this if you're reporting an issue", undefined);
}}
/>
-
+
"View Raw" is added to the message long-press menu by detecting Discord's internal
action-sheet layout, which isn't an official API and can change between app versions.
diff --git a/plugins/you-bar-plus/src/ui/Settings.tsx b/plugins/you-bar-plus/src/ui/Settings.tsx
index 3ec69d0..8f6ffa8 100644
--- a/plugins/you-bar-plus/src/ui/Settings.tsx
+++ b/plugins/you-bar-plus/src/ui/Settings.tsx
@@ -1,12 +1,11 @@
import { React } from "@vendetta/metro/common";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
-import { Forms } from "@vendetta/ui/components";
+import { TableRowGroup, TableSwitchRow } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import NoteBox from "@shared/ui/NoteBox";
import { updateYouBar } from "../patches/youBarButtons";
-const { FormSection, FormSwitchRow } = Forms;
export default function Settings() {
useProxy(storage);
@@ -21,8 +20,8 @@ export default function Settings() {
original, this patches the YouBar the moment it loads - if you enable it while
Discord's already running, you may need to restart once for the buttons to appear.
-
-
+
-
-
+
);
}
diff --git a/shared/ui/ListSection.tsx b/shared/ui/ListSection.tsx
index 872984e..8c4a11c 100644
--- a/shared/ui/ListSection.tsx
+++ b/shared/ui/ListSection.tsx
@@ -1,9 +1,8 @@
import { React, ReactNative } from "@vendetta/metro/common";
-import { Forms } from "@vendetta/ui/components";
+import { TableRow, TableRowGroup } from "./table";
import NoteBox from "./NoteBox";
const { Text } = ReactNative;
-const { FormSection, FormRow } = Forms;
export interface ListItem {
key: string;
@@ -19,12 +18,12 @@ export interface ListItem {
*/
export default function ListSection({ title, items, emptyText }: { title: string; items: ListItem[]; emptyText: string }) {
return (
-
+
{items.length === 0 ? (
{emptyText}
) : (
items.map((item) => (
-
))
)}
-
+
);
}
diff --git a/shared/ui/table.ts b/shared/ui/table.ts
new file mode 100644
index 0000000..ea61e75
--- /dev/null
+++ b/shared/ui/table.ts
@@ -0,0 +1,29 @@
+import { findByProps } from "@vendetta/metro";
+import { Forms } from "@vendetta/ui/components";
+
+/**
+ * Discord's redesigned "Table" row family (TableRow/TableRowGroup/TableSwitchRow/...) replaced
+ * the old Forms.Form* components in the app itself a while ago - Forms.* still works (Vendetta's
+ * compat layer maps it to the legacy components, see metro/common/components.ts in the loader),
+ * but it's the deprecated skin. These are found the same way ViewRaw finds ActionSheetRow: a
+ * direct findByProps lookup for the real component, since the vendetta.ui.components compat
+ * object never exposed the Table family at all. Falls back to the matching legacy Form
+ * component if a lookup ever comes back empty, same "isolate the failure" approach as the rest
+ * of this repo - the fallback's prop names don't perfectly match (e.g. `icon` vs `leading`), so a
+ * few things would render without an icon/arrow in that fallback path, but nothing crashes.
+ */
+const find = (prop: string): any => findByProps(prop)?.[prop];
+
+const RealTableRow: any = find("TableRow");
+
+export const TableRow: any = RealTableRow ?? Forms.FormRow;
+export const TableRowGroup: any = find("TableRowGroup") ?? Forms.FormSection;
+export const TableSwitchRow: any = find("TableSwitchRow") ?? Forms.FormSwitchRow;
+export const TableRadioRow: any = find("TableRadioRow") ?? Forms.FormRadioRow;
+export const TableRadioGroup: any = find("TableRadioGroup") ?? Forms.FormRadioGroup;
+export const TableCheckboxRow: any = find("TableCheckboxRow") ?? Forms.FormCheckboxRow;
+export const TextInput: any = find("TextInput") ?? Forms.FormInput;
+
+export const TableRowIcon: any = RealTableRow?.Icon ?? Forms.FormRow.Icon;
+export const TableRowArrow: any = RealTableRow?.Arrow ?? Forms.FormRow.Arrow;
+export const TableRowTrailingText: any = RealTableRow?.TrailingText;