refactor: replace settings buttons with switches and simplify provider sheet
Swap daemon management and keep-running-after-quit buttons for Switch toggles. Remove the restart/start daemon control. Simplify the provider diagnostic sheet to reuse shared settings styles and SettingsSection. Drop the unused "sm" size variant from the Switch component. Remove trailing periods from hint text for consistency.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { AlertCircle, Plus, RotateCw, Search, Trash2 } from "lucide-react-native";
|
||||
import { AlertCircle, RotateCw, Search, Trash2 } from "lucide-react-native";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -10,12 +10,15 @@ import {
|
||||
} from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { AdaptiveModalSheet, AdaptiveTextInput } from "@/components/adaptive-modal-sheet";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||
import { isWeb } from "@/constants/platform";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import { useDaemonConfig } from "@/hooks/use-daemon-config";
|
||||
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
|
||||
import { useHostRuntimeClient } from "@/runtime/host-runtime";
|
||||
import { SettingsSection } from "@/screens/settings/settings-section";
|
||||
import { settingsStyles } from "@/styles/settings";
|
||||
import { resolveProviderLabel } from "@/utils/provider-definitions";
|
||||
import { formatTimeAgo } from "@/utils/time";
|
||||
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||
@@ -28,35 +31,28 @@ interface ProviderDiagnosticSheetProps {
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
function ModelRow({ model, isFirst }: { model: AgentModelDefinition; isFirst: boolean }) {
|
||||
const rowStyle = useMemo(
|
||||
() => [sheetStyles.modelRow, !isFirst && sheetStyles.modelRowBorder],
|
||||
[isFirst],
|
||||
);
|
||||
function ModelRow({ model }: { model: AgentModelDefinition }) {
|
||||
return (
|
||||
<View style={rowStyle}>
|
||||
<Text style={sheetStyles.modelLabel} numberOfLines={1}>
|
||||
{model.label}
|
||||
</Text>
|
||||
<Text style={sheetStyles.modelId} numberOfLines={1} selectable>
|
||||
{model.id}
|
||||
</Text>
|
||||
<View style={MODEL_ROW_STYLE}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle} numberOfLines={1}>
|
||||
{model.label}
|
||||
</Text>
|
||||
<Text style={sheetStyles.monoHint} numberOfLines={1} selectable>
|
||||
{model.id}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function AdditionalModelRow(props: {
|
||||
function CustomModelRow(props: {
|
||||
model: ProviderProfileModel;
|
||||
isFirst: boolean;
|
||||
deleting: boolean;
|
||||
onDelete: (modelId: string) => void;
|
||||
}) {
|
||||
const { theme } = useUnistyles();
|
||||
const { model, isFirst, deleting, onDelete } = props;
|
||||
const rowStyle = useMemo(
|
||||
() => [sheetStyles.additionalModelRow, !isFirst && sheetStyles.modelRowBorder],
|
||||
[isFirst],
|
||||
);
|
||||
const { model, deleting, onDelete } = props;
|
||||
const handleDelete = useCallback(() => onDelete(model.id), [model.id, onDelete]);
|
||||
const deleteButtonStyle = useCallback(
|
||||
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||
@@ -68,12 +64,12 @@ function AdditionalModelRow(props: {
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={rowStyle}>
|
||||
<View style={sheetStyles.additionalModelText}>
|
||||
<Text style={sheetStyles.modelLabel} numberOfLines={1}>
|
||||
<View style={MODEL_ROW_STYLE}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle} numberOfLines={1}>
|
||||
{model.label}
|
||||
</Text>
|
||||
<Text style={sheetStyles.modelId} numberOfLines={1} selectable>
|
||||
<Text style={sheetStyles.monoHint} numberOfLines={1} selectable>
|
||||
{model.id}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -91,7 +87,7 @@ function AdditionalModelRow(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function AdditionalModelsEditor(props: {
|
||||
function CustomModelsSection(props: {
|
||||
provider: string;
|
||||
serverId: string;
|
||||
refresh: (providers?: AgentProvider[]) => Promise<void>;
|
||||
@@ -162,20 +158,10 @@ function AdditionalModelsEditor(props: {
|
||||
[additionalModels, patchAdditionalModels],
|
||||
);
|
||||
|
||||
const addButtonStyle = useCallback(
|
||||
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||
sheetStyles.addModelButton,
|
||||
(Boolean(hovered) || pressed) && sheetStyles.addModelButtonHovered,
|
||||
!canAdd || saving ? sheetStyles.disabled : null,
|
||||
],
|
||||
[canAdd, saving],
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={sheetStyles.section}>
|
||||
<Text style={sheetStyles.sectionTitle}>Additional Models</Text>
|
||||
<View style={sheetStyles.addModelRow}>
|
||||
<View style={sheetStyles.addModelInputContainer}>
|
||||
<SettingsSection title="Custom models">
|
||||
<View style={settingsStyles.card}>
|
||||
<View style={INLINE_ROW_STYLE}>
|
||||
<AdaptiveTextInput
|
||||
value={input}
|
||||
onChangeText={setInput}
|
||||
@@ -186,38 +172,29 @@ function AdditionalModelsEditor(props: {
|
||||
autoCorrect={false}
|
||||
returnKeyType="done"
|
||||
// @ts-expect-error - outlineStyle is web-only
|
||||
style={DIAGNOSTIC_ADD_MODEL_INPUT_STYLE}
|
||||
style={DIAGNOSTIC_INLINE_INPUT_STYLE}
|
||||
/>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onPress={handleAdd}
|
||||
disabled={!canAdd || saving}
|
||||
accessibilityLabel="Add model"
|
||||
>
|
||||
{saving ? "Adding…" : "Add"}
|
||||
</Button>
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={handleAdd}
|
||||
disabled={!canAdd || saving}
|
||||
style={addButtonStyle}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Add model"
|
||||
>
|
||||
{saving ? (
|
||||
<LoadingSpinner size={theme.iconSize.sm} color={theme.colors.accentForeground} />
|
||||
) : (
|
||||
<Plus size={theme.iconSize.sm} color={theme.colors.accentForeground} />
|
||||
)}
|
||||
</Pressable>
|
||||
{additionalModels.map((model) => (
|
||||
<CustomModelRow
|
||||
key={model.id}
|
||||
model={model}
|
||||
deleting={deletingModelId === model.id}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
{error ? <Text style={sheetStyles.errorText}>{error}</Text> : null}
|
||||
{additionalModels.length > 0 ? (
|
||||
<View style={sheetStyles.additionalModelsList}>
|
||||
{additionalModels.map((model, index) => (
|
||||
<AdditionalModelRow
|
||||
key={model.id}
|
||||
model={model}
|
||||
isFirst={index === 0}
|
||||
deleting={deletingModelId === model.id}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -251,7 +228,7 @@ function DiagnosticCodeBlock(props: {
|
||||
}
|
||||
return (
|
||||
<View style={sheetStyles.codeBlockLoading}>
|
||||
<Text style={sheetStyles.mutedText}>No diagnostic available.</Text>
|
||||
<Text style={sheetStyles.mutedText}>No diagnostic available</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -288,7 +265,6 @@ export function ProviderDiagnosticSheet({
|
||||
}, [visible]);
|
||||
const fetchedAtLabel = useMemo(() => {
|
||||
if (!providerEntry?.fetchedAt) return null;
|
||||
// clockTick is referenced so the label recomputes each timer tick.
|
||||
void clockTick;
|
||||
return formatTimeAgo(new Date(providerEntry.fetchedAt));
|
||||
}, [providerEntry?.fetchedAt, clockTick]);
|
||||
@@ -375,10 +351,27 @@ export function ProviderDiagnosticSheet({
|
||||
}
|
||||
}, [visible, fetchDiagnostic]);
|
||||
|
||||
const modelsTrailing = useMemo(() => {
|
||||
if (models.length === 0 && !fetchedAtLabel) return undefined;
|
||||
return (
|
||||
<View style={sheetStyles.modelsTrailing}>
|
||||
{models.length > 0 ? (
|
||||
<Text style={settingsStyles.sectionHeaderTitle}>{models.length}</Text>
|
||||
) : null}
|
||||
{models.length > 0 && fetchedAtLabel ? (
|
||||
<Text style={settingsStyles.sectionHeaderTitle}>·</Text>
|
||||
) : null}
|
||||
{fetchedAtLabel ? (
|
||||
<Text style={settingsStyles.sectionHeaderTitle}>Updated {fetchedAtLabel}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}, [models.length, fetchedAtLabel]);
|
||||
|
||||
function renderModelsBody() {
|
||||
if (models.length === 0 && providerSnapshotRefreshing) {
|
||||
return (
|
||||
<View style={sheetStyles.emptyState}>
|
||||
<View style={sheetStyles.emptyRow}>
|
||||
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
||||
<Text style={sheetStyles.mutedText}>Loading models…</Text>
|
||||
</View>
|
||||
@@ -386,7 +379,7 @@ export function ProviderDiagnosticSheet({
|
||||
}
|
||||
if (models.length === 0 && providerErrorMessage) {
|
||||
return (
|
||||
<View style={sheetStyles.emptyState}>
|
||||
<View style={sheetStyles.emptyRow}>
|
||||
<AlertCircle size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||
<Text style={sheetStyles.mutedText}>{providerErrorMessage}</Text>
|
||||
</View>
|
||||
@@ -394,21 +387,21 @@ export function ProviderDiagnosticSheet({
|
||||
}
|
||||
if (models.length === 0) {
|
||||
return (
|
||||
<View style={sheetStyles.emptyState}>
|
||||
<Text style={sheetStyles.mutedText}>No models detected.</Text>
|
||||
<View style={sheetStyles.emptyRow}>
|
||||
<Text style={sheetStyles.mutedText}>No models detected</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (filteredModels.length === 0) {
|
||||
return (
|
||||
<View style={sheetStyles.emptyState}>
|
||||
<View style={sheetStyles.emptyRow}>
|
||||
<Search size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||
<Text style={sheetStyles.mutedText}>No models match your search</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return filteredModels.map((model: AgentModelDefinition, index) => (
|
||||
<ModelRow key={model.id} model={model} isFirst={index === 0} />
|
||||
return filteredModels.map((model: AgentModelDefinition) => (
|
||||
<ModelRow key={model.id} model={model} />
|
||||
));
|
||||
}
|
||||
|
||||
@@ -421,35 +414,26 @@ export function ProviderDiagnosticSheet({
|
||||
scrollable={false}
|
||||
headerActions={headerActions}
|
||||
>
|
||||
<View style={sheetStyles.section}>
|
||||
<Text style={sheetStyles.sectionTitle}>Diagnostic</Text>
|
||||
<View style={sheetStyles.codeBlock}>
|
||||
<SettingsSection title="Diagnostic">
|
||||
<View style={settingsStyles.card}>
|
||||
<DiagnosticCodeBlock
|
||||
loading={loading}
|
||||
diagnostic={diagnostic}
|
||||
foregroundMutedColor={theme.colors.foregroundMuted}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</SettingsSection>
|
||||
|
||||
<CustomModelsSection provider={provider} serverId={serverId} refresh={refresh} />
|
||||
|
||||
<View style={sheetStyles.modelsSection}>
|
||||
<AdditionalModelsEditor provider={provider} serverId={serverId} refresh={refresh} />
|
||||
|
||||
<View style={sheetStyles.modelsHeader}>
|
||||
<Text style={sheetStyles.sectionTitle}>Models</Text>
|
||||
<View style={sheetStyles.modelsHeaderMeta}>
|
||||
<Text style={sheetStyles.countText}>{models.length}</Text>
|
||||
{fetchedAtLabel ? (
|
||||
<>
|
||||
<Text style={sheetStyles.metaDot}>·</Text>
|
||||
<Text style={sheetStyles.countText}>Updated {fetchedAtLabel}</Text>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
<Text style={settingsStyles.sectionHeaderTitle}>Models</Text>
|
||||
{modelsTrailing}
|
||||
</View>
|
||||
{models.length > 0 ? (
|
||||
<View style={sheetStyles.searchContainer}>
|
||||
<Search size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||
<View style={MODELS_CARD_STYLE}>
|
||||
<View style={INLINE_ROW_STYLE}>
|
||||
<Search size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
||||
<AdaptiveTextInput
|
||||
value={query}
|
||||
onChangeText={setQuery}
|
||||
@@ -461,36 +445,36 @@ export function ProviderDiagnosticSheet({
|
||||
style={DIAGNOSTIC_SEARCH_INPUT_STYLE}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
<ScrollView
|
||||
style={sheetStyles.modelsScroll}
|
||||
contentContainerStyle={sheetStyles.modelsScrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{renderModelsBody()}
|
||||
</ScrollView>
|
||||
<ScrollView
|
||||
style={sheetStyles.modelsScroll}
|
||||
contentContainerStyle={sheetStyles.modelsScrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{renderModelsBody()}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</AdaptiveModalSheet>
|
||||
);
|
||||
}
|
||||
|
||||
const sheetStyles = StyleSheet.create((theme) => ({
|
||||
section: {
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
sectionTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
},
|
||||
mutedText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
monoHint: {
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
marginTop: theme.spacing[1],
|
||||
},
|
||||
errorText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.destructive,
|
||||
marginTop: theme.spacing[2],
|
||||
marginLeft: theme.spacing[1],
|
||||
},
|
||||
iconButton: {
|
||||
width: 30,
|
||||
@@ -505,20 +489,24 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
||||
disabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
codeBlock: {
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: theme.borderRadius.base,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
overflow: "hidden",
|
||||
maxHeight: 180,
|
||||
inlineRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
inlineInput: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
paddingVertical: 0,
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
codeScroll: {
|
||||
maxHeight: 180,
|
||||
maxHeight: 200,
|
||||
},
|
||||
codeContent: {
|
||||
paddingVertical: theme.spacing[3],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingHorizontal: theme.spacing[4],
|
||||
},
|
||||
codeText: {
|
||||
fontFamily: Fonts.mono,
|
||||
@@ -528,7 +516,7 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
codeBlockLoading: {
|
||||
paddingVertical: theme.spacing[4],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingHorizontal: theme.spacing[4],
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
@@ -536,118 +524,46 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
||||
modelsSection: {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
modelsHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: theme.spacing[2],
|
||||
marginBottom: theme.spacing[3],
|
||||
marginLeft: theme.spacing[1],
|
||||
},
|
||||
modelsHeaderMeta: {
|
||||
flexCard: {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
},
|
||||
modelsTrailing: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
metaDot: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
countText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
searchContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
},
|
||||
addModelRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
addModelInputContainer: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
},
|
||||
addModelInput: {
|
||||
paddingVertical: theme.spacing[2],
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
addModelButton: {
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.colors.accent,
|
||||
},
|
||||
addModelButtonHovered: {
|
||||
backgroundColor: theme.colors.accentBright,
|
||||
},
|
||||
additionalModelsList: {
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.border,
|
||||
},
|
||||
additionalModelRow: {
|
||||
minHeight: 48,
|
||||
paddingVertical: theme.spacing[2],
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
additionalModelText: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
paddingVertical: theme.spacing[2],
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
modelsScroll: {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
},
|
||||
modelsScrollContent: {
|
||||
paddingBottom: theme.spacing[2],
|
||||
paddingBottom: 0,
|
||||
},
|
||||
modelRow: {
|
||||
paddingVertical: theme.spacing[3],
|
||||
},
|
||||
modelRowBorder: {
|
||||
emptyRow: {
|
||||
paddingVertical: theme.spacing[6],
|
||||
paddingHorizontal: theme.spacing[4],
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: theme.spacing[2],
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: theme.colors.border,
|
||||
},
|
||||
modelLabel: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
modelId: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontFamily: Fonts.mono,
|
||||
marginTop: 2,
|
||||
},
|
||||
emptyState: {
|
||||
paddingVertical: theme.spacing[6],
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
}));
|
||||
|
||||
const DIAGNOSTIC_SHEET_SNAP_POINTS = ["50%", "85%"];
|
||||
const DIAGNOSTIC_SEARCH_INPUT_STYLE = [sheetStyles.searchInput, isWeb && { outlineStyle: "none" }];
|
||||
const DIAGNOSTIC_ADD_MODEL_INPUT_STYLE = [
|
||||
sheetStyles.addModelInput,
|
||||
isWeb && { outlineStyle: "none" },
|
||||
];
|
||||
const DIAGNOSTIC_SEARCH_INPUT_STYLE = [sheetStyles.inlineInput, isWeb && { outlineStyle: "none" }];
|
||||
const DIAGNOSTIC_INLINE_INPUT_STYLE = [sheetStyles.inlineInput, isWeb && { outlineStyle: "none" }];
|
||||
const MODEL_ROW_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
const INLINE_ROW_STYLE = [settingsStyles.row, sheetStyles.inlineRow];
|
||||
const MODELS_CARD_STYLE = [settingsStyles.card, sheetStyles.flexCard];
|
||||
|
||||
@@ -14,22 +14,17 @@ import Animated, {
|
||||
} from "react-native-reanimated";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
|
||||
type SwitchSize = "sm" | "md";
|
||||
|
||||
interface SwitchProps {
|
||||
value: boolean;
|
||||
onValueChange?: (value: boolean) => void;
|
||||
disabled?: boolean;
|
||||
size?: SwitchSize;
|
||||
accessibilityLabel?: string;
|
||||
testID?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const SIZES: Record<SwitchSize, { track: { width: number; height: number }; thumb: number }> = {
|
||||
md: { track: { width: 44, height: 26 }, thumb: 22 },
|
||||
sm: { track: { width: 34, height: 20 }, thumb: 16 },
|
||||
};
|
||||
const TRACK = { width: 34, height: 20 };
|
||||
const THUMB = 16;
|
||||
|
||||
const TIMING = { duration: 180, easing: Easing.inOut(Easing.ease) };
|
||||
|
||||
@@ -37,13 +32,13 @@ export function Switch({
|
||||
value,
|
||||
onValueChange,
|
||||
disabled = false,
|
||||
size = "md",
|
||||
accessibilityLabel,
|
||||
testID,
|
||||
style,
|
||||
}: SwitchProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const { track, thumb } = SIZES[size];
|
||||
const track = TRACK;
|
||||
const thumb = THUMB;
|
||||
const padding = (track.height - thumb) / 2;
|
||||
const thumbTravel = track.width - thumb - padding * 2;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user