Remove custom providers from settings (#1951)
* feat(providers): remove custom providers from settings Add a destructive removal flow so mistaken custom providers can be deleted from config.json instead of only disabled. * test(app): cover provider removal with e2e Move provider removal coverage out of mocked component tests and into the real Settings flow. * fix(providers): keep removal live after config updates
This commit is contained in:
@@ -1909,6 +1909,17 @@ export const ar: TranslationResources = {
|
||||
loading: "تحميل...",
|
||||
addErrorTitle: "Unable to add provider",
|
||||
updateErrorTitle: "غير قادر على تحديث الموفر",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "عاجز",
|
||||
loading: "تحميل",
|
||||
|
||||
@@ -1918,6 +1918,17 @@ export const en = {
|
||||
loading: "Loading...",
|
||||
addErrorTitle: "Unable to add provider",
|
||||
updateErrorTitle: "Unable to update provider",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "Disabled",
|
||||
loading: "Loading",
|
||||
|
||||
@@ -1956,6 +1956,17 @@ export const es: TranslationResources = {
|
||||
loading: "Cargando...",
|
||||
addErrorTitle: "Unable to add provider",
|
||||
updateErrorTitle: "No se puede actualizar el proveedor",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "Desactivado",
|
||||
loading: "Cargando",
|
||||
|
||||
@@ -1959,6 +1959,17 @@ export const fr: TranslationResources = {
|
||||
loading: "Chargement...",
|
||||
addErrorTitle: "Unable to add provider",
|
||||
updateErrorTitle: "Impossible de mettre à jour le fournisseur",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "Désactivé",
|
||||
loading: "Chargement",
|
||||
|
||||
@@ -1931,6 +1931,17 @@ export const ja: TranslationResources = {
|
||||
loading: "読み込み中...",
|
||||
addErrorTitle: "プロバイダーを追加できません",
|
||||
updateErrorTitle: "プロバイダーを更新できません",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "無効",
|
||||
loading: "読み込み中",
|
||||
|
||||
@@ -1941,6 +1941,17 @@ export const ptBR: TranslationResources = {
|
||||
loading: "Carregando...",
|
||||
addErrorTitle: "Não foi possível adicionar provedor",
|
||||
updateErrorTitle: "Não foi possível atualizar provedor",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "Desativado",
|
||||
loading: "Carregando",
|
||||
|
||||
@@ -1946,6 +1946,17 @@ export const ru: TranslationResources = {
|
||||
loading: "Загрузка...",
|
||||
addErrorTitle: "Unable to add provider",
|
||||
updateErrorTitle: "Невозможно обновить провайдера",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "Неполноценный",
|
||||
loading: "Загрузка",
|
||||
|
||||
@@ -1886,6 +1886,17 @@ export const zhCN: TranslationResources = {
|
||||
loading: "正在加载...",
|
||||
addErrorTitle: "无法添加 Provider",
|
||||
updateErrorTitle: "无法更新 Provider",
|
||||
actions: {
|
||||
menu: "{{name}} actions",
|
||||
remove: "Remove provider",
|
||||
removing: "Removing...",
|
||||
},
|
||||
remove: {
|
||||
confirmTitle: "Remove {{name}}?",
|
||||
confirmMessage: "This deletes the provider entry from config.json. It cannot be undone.",
|
||||
confirm: "Remove",
|
||||
errorTitle: "Unable to remove provider",
|
||||
},
|
||||
statuses: {
|
||||
disabled: "已禁用",
|
||||
loading: "正在加载",
|
||||
|
||||
@@ -97,25 +97,39 @@ vi.mock("lucide-react-native", () => {
|
||||
const icon = (name: string) => () => React.createElement("span", { "data-icon": name });
|
||||
return {
|
||||
ChevronRight: icon("ChevronRight"),
|
||||
MoreHorizontal: icon("MoreHorizontal"),
|
||||
Trash2: icon("Trash2"),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, values?: Record<string, string | number>) => {
|
||||
if (key === "settings.providers.providerDetails") return `${values?.name} provider details`;
|
||||
if (key === "settings.providers.enableProvider") return `Enable ${values?.name}`;
|
||||
if (key === "settings.providers.statuses.disabled") return "Disabled";
|
||||
if (key === "settings.providers.statuses.available") return "Available";
|
||||
if (key === "settings.providers.statuses.loading") return "Loading";
|
||||
if (key === "settings.providers.statuses.error") return "Error";
|
||||
if (key === "settings.providers.statuses.notInstalled") return "Not installed";
|
||||
if (key === "settings.providers.models.one") return "1 model";
|
||||
if (key === "settings.providers.models.many") return `${values?.count} models`;
|
||||
if (key === "settings.providers.addErrorTitle") return "Unable to add provider";
|
||||
if (key === "settings.providers.updateErrorTitle") return "Unable to update provider";
|
||||
return key;
|
||||
},
|
||||
t: (key: string, values?: Record<string, string | number>) =>
|
||||
(
|
||||
({
|
||||
"settings.providers.providerDetails": "{{name}} provider details",
|
||||
"settings.providers.enableProvider": "Enable {{name}}",
|
||||
"settings.providers.statuses.disabled": "Disabled",
|
||||
"settings.providers.statuses.available": "Available",
|
||||
"settings.providers.statuses.loading": "Loading",
|
||||
"settings.providers.statuses.error": "Error",
|
||||
"settings.providers.statuses.notInstalled": "Not installed",
|
||||
"settings.providers.models.one": "1 model",
|
||||
"settings.providers.models.many": "{{count}} models",
|
||||
"settings.providers.addErrorTitle": "Unable to add provider",
|
||||
"settings.providers.updateErrorTitle": "Unable to update provider",
|
||||
"settings.providers.actions.menu": "{{name}} actions",
|
||||
"settings.providers.actions.remove": "Remove provider",
|
||||
"settings.providers.actions.removing": "Removing...",
|
||||
"settings.providers.remove.confirmTitle": "Remove {{name}}?",
|
||||
"settings.providers.remove.confirmMessage":
|
||||
"This deletes the provider entry from config.json. It cannot be undone.",
|
||||
"settings.providers.remove.confirm": "Remove",
|
||||
"settings.providers.remove.errorTitle": "Unable to remove provider",
|
||||
})[key] ?? key
|
||||
)
|
||||
.replaceAll("{{name}}", String(values?.name ?? ""))
|
||||
.replaceAll("{{count}}", String(values?.count ?? "")),
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -151,6 +165,68 @@ vi.mock("@/components/ui/loading-spinner", () => ({
|
||||
LoadingSpinner: () => React.createElement("span", { "data-testid": "loading-spinner" }),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/ui/dropdown-menu", () => ({
|
||||
DropdownMenu: ({ children }: { children?: React.ReactNode }) =>
|
||||
React.createElement("div", null, children),
|
||||
DropdownMenuTrigger: ({
|
||||
children,
|
||||
onPressIn,
|
||||
accessibilityRole,
|
||||
accessibilityLabel,
|
||||
testID,
|
||||
}: {
|
||||
children?:
|
||||
| React.ReactNode
|
||||
| ((state: { pressed: boolean; hovered: boolean; open: boolean }) => React.ReactNode);
|
||||
onPressIn?: (event: { stopPropagation: () => void }) => void;
|
||||
accessibilityRole?: string;
|
||||
accessibilityLabel?: string;
|
||||
testID?: string;
|
||||
}) =>
|
||||
React.createElement(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
role: accessibilityRole,
|
||||
"aria-label": accessibilityLabel,
|
||||
"data-testid": testID,
|
||||
onMouseDown: (event: React.MouseEvent) => onPressIn?.(event),
|
||||
onClick: (event: React.MouseEvent) => event.stopPropagation(),
|
||||
},
|
||||
typeof children === "function"
|
||||
? children({ pressed: false, hovered: false, open: false })
|
||||
: children,
|
||||
),
|
||||
DropdownMenuContent: ({ children }: { children?: React.ReactNode }) =>
|
||||
React.createElement("div", null, children),
|
||||
DropdownMenuItem: ({
|
||||
children,
|
||||
onSelect,
|
||||
status,
|
||||
pendingLabel,
|
||||
testID,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
onSelect?: () => void;
|
||||
status?: "idle" | "pending" | "success";
|
||||
pendingLabel?: string;
|
||||
testID?: string;
|
||||
}) =>
|
||||
React.createElement(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
"data-testid": testID,
|
||||
disabled: status === "pending" || status === "success",
|
||||
onClick: (event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
onSelect?.();
|
||||
},
|
||||
},
|
||||
status === "pending" ? pendingLabel : children,
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/provider-icons", () => ({
|
||||
getProviderIcon: (provider: string) => () =>
|
||||
React.createElement("span", { "data-icon": `provider-${provider}` }),
|
||||
@@ -190,6 +266,14 @@ vi.mock("@/runtime/host-runtime", () => ({
|
||||
useHostRuntimeIsConnected: () => true,
|
||||
}));
|
||||
|
||||
vi.mock("@/runtime/host-features", () => ({
|
||||
useHostFeature: () => false,
|
||||
}));
|
||||
|
||||
vi.mock("@/utils/confirm-dialog", () => ({
|
||||
confirmDialog: vi.fn(async () => true),
|
||||
}));
|
||||
|
||||
import { ProvidersSection } from "./providers-section";
|
||||
|
||||
const claudeEntry: ProviderSnapshotEntry = {
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import type { TFunction } from "i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Alert, Pressable, Text, View, type PressableStateCallbackType } from "react-native";
|
||||
import {
|
||||
Alert,
|
||||
Pressable,
|
||||
Text,
|
||||
View,
|
||||
type GestureResponderEvent,
|
||||
type PressableStateCallbackType,
|
||||
} from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { settingsStyles } from "@/styles/settings";
|
||||
import { useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||
import { useHostFeature } from "@/runtime/host-features";
|
||||
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
|
||||
import { useDaemonConfig } from "@/hooks/use-daemon-config";
|
||||
import { buildProviderDefinitions } from "@/utils/provider-definitions";
|
||||
@@ -16,9 +24,16 @@ import { ProviderCatalogList } from "@/components/provider-catalog-list";
|
||||
import { getProviderIcon } from "@/components/provider-icons";
|
||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { SettingsSection } from "@/screens/settings/settings-section";
|
||||
import { useProviderSettingsStore } from "@/stores/provider-settings-store";
|
||||
import { ChevronRight } from "lucide-react-native";
|
||||
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||
import { ChevronRight, MoreHorizontal, Trash2 } from "lucide-react-native";
|
||||
|
||||
type ProviderDefinition = ReturnType<typeof buildProviderDefinitions>[number];
|
||||
type ProviderEntry = NonNullable<ReturnType<typeof useProvidersSnapshot>["entries"]>[number];
|
||||
@@ -64,9 +79,89 @@ interface ProviderRowProps {
|
||||
entry: ProviderEntry;
|
||||
enabled: boolean;
|
||||
isToggling: boolean;
|
||||
isRemoving: boolean;
|
||||
canRemove: boolean;
|
||||
isFirst: boolean;
|
||||
onPress: (providerId: string) => void;
|
||||
onToggleEnabled: (providerId: string, enabled: boolean) => void;
|
||||
onRemove: (providerId: string, providerLabel: string) => void;
|
||||
}
|
||||
|
||||
function stopPressInPropagation(event: GestureResponderEvent) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
interface ProviderActionsMenuProps {
|
||||
providerId: string;
|
||||
providerLabel: string;
|
||||
isRemoving: boolean;
|
||||
iconSize: number;
|
||||
foregroundColor: string;
|
||||
foregroundMutedColor: string;
|
||||
dangerColor: string;
|
||||
onRemove: (providerId: string, providerLabel: string) => void;
|
||||
}
|
||||
|
||||
function ProviderActionsMenu({
|
||||
providerId,
|
||||
providerLabel,
|
||||
isRemoving,
|
||||
iconSize,
|
||||
foregroundColor,
|
||||
foregroundMutedColor,
|
||||
dangerColor,
|
||||
onRemove,
|
||||
}: ProviderActionsMenuProps) {
|
||||
const { t } = useTranslation();
|
||||
const handleRemove = useCallback(() => {
|
||||
onRemove(providerId, providerLabel);
|
||||
}, [onRemove, providerId, providerLabel]);
|
||||
const triggerStyle = useCallback(
|
||||
({
|
||||
pressed,
|
||||
hovered,
|
||||
open,
|
||||
}: PressableStateCallbackType & { hovered?: boolean; open?: boolean }) => [
|
||||
styles.menuButton,
|
||||
(hovered || open) && styles.menuButtonHovered,
|
||||
pressed && styles.menuButtonPressed,
|
||||
],
|
||||
[],
|
||||
);
|
||||
const trashLeading = useMemo(() => <Trash2 size={16} color={dangerColor} />, [dangerColor]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
disabled={isRemoving}
|
||||
hitSlop={8}
|
||||
onPressIn={stopPressInPropagation}
|
||||
style={triggerStyle}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t("settings.providers.actions.menu", { name: providerLabel })}
|
||||
testID={`provider-actions-${providerId}`}
|
||||
>
|
||||
{({ hovered, open }) => (
|
||||
<MoreHorizontal
|
||||
size={iconSize}
|
||||
color={hovered || open ? foregroundColor : foregroundMutedColor}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" width={220}>
|
||||
<DropdownMenuItem
|
||||
destructive
|
||||
leading={trashLeading}
|
||||
onSelect={handleRemove}
|
||||
status={isRemoving ? "pending" : "idle"}
|
||||
pendingLabel={t("settings.providers.actions.removing")}
|
||||
testID={`provider-remove-${providerId}`}
|
||||
>
|
||||
{t("settings.providers.actions.remove")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
function ProviderRow({
|
||||
@@ -74,9 +169,12 @@ function ProviderRow({
|
||||
entry,
|
||||
enabled,
|
||||
isToggling,
|
||||
isRemoving,
|
||||
canRemove,
|
||||
isFirst,
|
||||
onPress,
|
||||
onToggleEnabled,
|
||||
onRemove,
|
||||
}: ProviderRowProps) {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useUnistyles();
|
||||
@@ -141,12 +239,26 @@ function ProviderRow({
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
<Switch
|
||||
value={enabled}
|
||||
onValueChange={handleToggleValueChange}
|
||||
disabled={isToggling}
|
||||
accessibilityLabel={t("settings.providers.enableProvider", { name: def.label })}
|
||||
/>
|
||||
<View style={styles.trailingControls}>
|
||||
<Switch
|
||||
value={enabled}
|
||||
onValueChange={handleToggleValueChange}
|
||||
disabled={isToggling || isRemoving}
|
||||
accessibilityLabel={t("settings.providers.enableProvider", { name: def.label })}
|
||||
/>
|
||||
{canRemove ? (
|
||||
<ProviderActionsMenu
|
||||
providerId={def.id}
|
||||
providerLabel={def.label}
|
||||
isRemoving={isRemoving}
|
||||
iconSize={theme.iconSize.sm}
|
||||
foregroundColor={theme.colors.foreground}
|
||||
foregroundMutedColor={theme.colors.foregroundMuted}
|
||||
dangerColor={theme.colors.statusDanger}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
@@ -203,10 +315,13 @@ export interface ProvidersSectionProps {
|
||||
export function ProvidersSection({ serverId }: ProvidersSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||
const supportsProviderRemoval = useHostFeature(serverId, "providerRemoval");
|
||||
const { entries, isLoading, refresh } = useProvidersSnapshot(serverId);
|
||||
const { patchConfig } = useDaemonConfig(serverId);
|
||||
const openProviderSettings = useProviderSettingsStore((state) => state.open);
|
||||
const [pendingProviderId, setPendingProviderId] = useState<string | null>(null);
|
||||
const [removingProviderId, setRemovingProviderId] = useState<string | null>(null);
|
||||
const removingProviderIdRef = useRef<string | null>(null);
|
||||
const [installingProviderId, setInstallingProviderId] = useState<string | null>(null);
|
||||
|
||||
const providerDefinitions = useMemo(() => buildProviderDefinitions(entries), [entries]);
|
||||
@@ -236,6 +351,38 @@ export function ProvidersSection({ serverId }: ProvidersSectionProps) {
|
||||
[patchConfig, t],
|
||||
);
|
||||
|
||||
const handleRemoveProvider = useCallback(
|
||||
async (providerId: string, providerLabel: string) => {
|
||||
if (removingProviderIdRef.current) return;
|
||||
removingProviderIdRef.current = providerId;
|
||||
setRemovingProviderId(providerId);
|
||||
try {
|
||||
const confirmed = await confirmDialog({
|
||||
title: t("settings.providers.remove.confirmTitle", { name: providerLabel }),
|
||||
message: t("settings.providers.remove.confirmMessage"),
|
||||
confirmLabel: t("settings.providers.remove.confirm"),
|
||||
destructive: true,
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await patchConfig({ removeProviders: [providerId] });
|
||||
} catch (error) {
|
||||
Alert.alert(
|
||||
t("settings.providers.remove.errorTitle"),
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
} finally {
|
||||
if (removingProviderIdRef.current === providerId) {
|
||||
removingProviderIdRef.current = null;
|
||||
}
|
||||
setRemovingProviderId((current) => (current === providerId ? null : current));
|
||||
}
|
||||
},
|
||||
[patchConfig, t],
|
||||
);
|
||||
|
||||
const handleInstall = useCallback(
|
||||
async (entry: AcpProviderCatalogItem) => {
|
||||
if (installingProviderId) return;
|
||||
@@ -284,9 +431,12 @@ export function ProvidersSection({ serverId }: ProvidersSectionProps) {
|
||||
entry={entry}
|
||||
enabled={entry.enabled ?? true}
|
||||
isToggling={pendingProviderId === def.id}
|
||||
isRemoving={removingProviderId === def.id}
|
||||
canRemove={supportsProviderRemoval && entry.source === "custom"}
|
||||
isFirst={index === 0}
|
||||
onPress={handleOpenProviderSettings}
|
||||
onToggleEnabled={handleToggleEnabled}
|
||||
onRemove={handleRemoveProvider}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -374,6 +524,24 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.xs,
|
||||
marginTop: theme.spacing[1],
|
||||
},
|
||||
trailingControls: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
menuButton: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
menuButtonHovered: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
menuButtonPressed: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
},
|
||||
}));
|
||||
|
||||
const EMPTY_CARD_STYLE = [settingsStyles.card, styles.emptyCard];
|
||||
|
||||
Reference in New Issue
Block a user