refactor: extract settings styles and improve badge/button UI
This commit is contained in:
@@ -646,6 +646,9 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
|||||||
marginLeft: theme.spacing[1],
|
marginLeft: theme.spacing[1],
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
|
chevronExpanded: {
|
||||||
|
transform: [{ rotate: "90deg" }],
|
||||||
|
},
|
||||||
detailWrapper: {
|
detailWrapper: {
|
||||||
borderBottomLeftRadius: theme.borderRadius.lg,
|
borderBottomLeftRadius: theme.borderRadius.lg,
|
||||||
borderBottomRightRadius: theme.borderRadius.lg,
|
borderBottomRightRadius: theme.borderRadius.lg,
|
||||||
@@ -1289,6 +1292,8 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
|||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const resolvedDisableOuterSpacing =
|
const resolvedDisableOuterSpacing =
|
||||||
useDisableOuterSpacing(disableOuterSpacing);
|
useDisableOuterSpacing(disableOuterSpacing);
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const [isPressed, setIsPressed] = useState(false);
|
||||||
const isInteractive = Boolean(onToggle);
|
const isInteractive = Boolean(onToggle);
|
||||||
const hasDetailContent = Boolean(renderDetails);
|
const hasDetailContent = Boolean(renderDetails);
|
||||||
const detailContent =
|
const detailContent =
|
||||||
@@ -1479,6 +1484,107 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
|||||||
} as never)
|
} as never)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const containerStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.container,
|
||||||
|
!resolvedDisableOuterSpacing &&
|
||||||
|
(isLastInSequence
|
||||||
|
? expandableBadgeStylesheet.containerLastInSequence
|
||||||
|
: expandableBadgeStylesheet.containerSpacing),
|
||||||
|
style,
|
||||||
|
],
|
||||||
|
[isLastInSequence, resolvedDisableOuterSpacing, style]
|
||||||
|
);
|
||||||
|
|
||||||
|
const pressableStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.pressable,
|
||||||
|
isPressed && isInteractive
|
||||||
|
? expandableBadgeStylesheet.pressablePressed
|
||||||
|
: null,
|
||||||
|
isExpanded && expandableBadgeStylesheet.pressableExpanded,
|
||||||
|
],
|
||||||
|
[isExpanded, isInteractive, isPressed]
|
||||||
|
);
|
||||||
|
|
||||||
|
const accessibilityState = useMemo(
|
||||||
|
() => (isInteractive ? { expanded: isExpanded } : undefined),
|
||||||
|
[isExpanded, isInteractive]
|
||||||
|
);
|
||||||
|
|
||||||
|
const labelStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.label,
|
||||||
|
isLoading && expandableBadgeStylesheet.labelLoading,
|
||||||
|
],
|
||||||
|
[isLoading]
|
||||||
|
);
|
||||||
|
|
||||||
|
const shimmerLabelTextStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.label,
|
||||||
|
isLoading && expandableBadgeStylesheet.labelLoading,
|
||||||
|
expandableBadgeStylesheet.shimmerText,
|
||||||
|
shimmerLabelStyle,
|
||||||
|
],
|
||||||
|
[isLoading, shimmerLabelStyle]
|
||||||
|
);
|
||||||
|
|
||||||
|
const shimmerSecondaryTextStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.secondaryLabel,
|
||||||
|
expandableBadgeStylesheet.shimmerText,
|
||||||
|
shimmerSecondaryStyle,
|
||||||
|
],
|
||||||
|
[shimmerSecondaryStyle]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nativeShimmerTrackStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.nativeShimmerTrack,
|
||||||
|
{ width: labelRowWidth, height: labelRowHeight },
|
||||||
|
],
|
||||||
|
[labelRowHeight, labelRowWidth]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nativeShimmerMaskStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.shimmerMaskRow,
|
||||||
|
{ width: labelRowWidth, height: labelRowHeight },
|
||||||
|
],
|
||||||
|
[labelRowHeight, labelRowWidth]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nativeLabelMaskStyle = useMemo(
|
||||||
|
() => [expandableBadgeStylesheet.label, { color: "#000000", opacity: 1 }],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nativeSecondaryMaskStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.secondaryLabel,
|
||||||
|
{ color: "#000000", opacity: 1 },
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nativeShimmerPeakCombinedStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.nativeShimmerPeak,
|
||||||
|
nativeShimmerPeakStyle,
|
||||||
|
{ width: nativeShimmerPeakWidth, height: labelRowHeight },
|
||||||
|
],
|
||||||
|
[labelRowHeight, nativeShimmerPeakStyle, nativeShimmerPeakWidth]
|
||||||
|
);
|
||||||
|
|
||||||
|
const chevronStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
expandableBadgeStylesheet.chevron,
|
||||||
|
isExpanded && expandableBadgeStylesheet.chevronExpanded,
|
||||||
|
],
|
||||||
|
[isExpanded]
|
||||||
|
);
|
||||||
|
|
||||||
const IconComponent = icon;
|
const IconComponent = icon;
|
||||||
const iconColor = isError
|
const iconColor = isError
|
||||||
? theme.colors.destructive
|
? theme.colors.destructive
|
||||||
@@ -1493,186 +1599,142 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={containerStyle}
|
||||||
expandableBadgeStylesheet.container,
|
|
||||||
!resolvedDisableOuterSpacing &&
|
|
||||||
(isLastInSequence
|
|
||||||
? expandableBadgeStylesheet.containerLastInSequence
|
|
||||||
: expandableBadgeStylesheet.containerSpacing),
|
|
||||||
style,
|
|
||||||
]}
|
|
||||||
testID={testID}
|
testID={testID}
|
||||||
>
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={isInteractive ? onToggle : undefined}
|
onPress={isInteractive ? onToggle : undefined}
|
||||||
|
onHoverIn={isInteractive ? () => setIsHovered(true) : undefined}
|
||||||
|
onHoverOut={
|
||||||
|
isInteractive
|
||||||
|
? () => {
|
||||||
|
setIsHovered(false);
|
||||||
|
setIsPressed(false);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onPressIn={isInteractive ? () => setIsPressed(true) : undefined}
|
||||||
|
onPressOut={isInteractive ? () => setIsPressed(false) : undefined}
|
||||||
disabled={!isInteractive}
|
disabled={!isInteractive}
|
||||||
accessibilityRole={isInteractive ? "button" : undefined}
|
accessibilityRole={isInteractive ? "button" : undefined}
|
||||||
accessibilityState={isInteractive ? { expanded: isExpanded } : undefined}
|
accessibilityState={accessibilityState}
|
||||||
style={({ pressed }) => [
|
style={pressableStyle}
|
||||||
expandableBadgeStylesheet.pressable,
|
|
||||||
pressed && isInteractive
|
|
||||||
? expandableBadgeStylesheet.pressablePressed
|
|
||||||
: null,
|
|
||||||
isExpanded && expandableBadgeStylesheet.pressableExpanded,
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
{({ hovered }) => (
|
<View style={expandableBadgeStylesheet.headerRow}>
|
||||||
<>
|
<View style={expandableBadgeStylesheet.iconBadge}>{iconNode}</View>
|
||||||
<View style={expandableBadgeStylesheet.headerRow}>
|
<View
|
||||||
<View style={expandableBadgeStylesheet.iconBadge}>{iconNode}</View>
|
style={expandableBadgeStylesheet.labelRow}
|
||||||
|
onLayout={shouldMeasureNativeShimmer ? handleLabelRowLayout : undefined}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={labelStyle}
|
||||||
|
numberOfLines={1}
|
||||||
|
onLayout={shouldMeasureWebShimmer ? handleLabelLayout : undefined}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
{secondaryLabel ? (
|
||||||
|
<Text
|
||||||
|
style={expandableBadgeStylesheet.secondaryLabel}
|
||||||
|
numberOfLines={1}
|
||||||
|
onLayout={shouldMeasureWebShimmer ? handleSecondaryLayout : undefined}
|
||||||
|
>
|
||||||
|
{secondaryLabel}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<View style={expandableBadgeStylesheet.spacer} />
|
||||||
|
)}
|
||||||
|
{isWebShimmer ? (
|
||||||
<View
|
<View
|
||||||
style={expandableBadgeStylesheet.labelRow}
|
style={expandableBadgeStylesheet.shimmerOverlay}
|
||||||
onLayout={shouldMeasureNativeShimmer ? handleLabelRowLayout : undefined}
|
pointerEvents="none"
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={shimmerLabelTextStyle}
|
||||||
expandableBadgeStylesheet.label,
|
|
||||||
isLoading && expandableBadgeStylesheet.labelLoading,
|
|
||||||
]}
|
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
onLayout={shouldMeasureWebShimmer ? handleLabelLayout : undefined}
|
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
{secondaryLabel ? (
|
{secondaryLabel ? (
|
||||||
<Text
|
<Text
|
||||||
style={expandableBadgeStylesheet.secondaryLabel}
|
style={shimmerSecondaryTextStyle}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
onLayout={shouldMeasureWebShimmer ? handleSecondaryLayout : undefined}
|
|
||||||
>
|
>
|
||||||
{secondaryLabel}
|
{secondaryLabel}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<View style={expandableBadgeStylesheet.spacer} />
|
<View style={expandableBadgeStylesheet.spacer} />
|
||||||
)}
|
)}
|
||||||
{isWebShimmer ? (
|
</View>
|
||||||
<View
|
) : null}
|
||||||
style={expandableBadgeStylesheet.shimmerOverlay}
|
{isNativeShimmer ? (
|
||||||
pointerEvents="none"
|
<View
|
||||||
>
|
style={expandableBadgeStylesheet.shimmerOverlay}
|
||||||
<Text
|
pointerEvents="none"
|
||||||
style={[
|
>
|
||||||
expandableBadgeStylesheet.label,
|
<MaskedView
|
||||||
isLoading && expandableBadgeStylesheet.labelLoading,
|
style={nativeShimmerTrackStyle}
|
||||||
expandableBadgeStylesheet.shimmerText,
|
maskElement={
|
||||||
shimmerLabelStyle,
|
<View style={nativeShimmerMaskStyle}>
|
||||||
]}
|
|
||||||
numberOfLines={1}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
{secondaryLabel ? (
|
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={nativeLabelMaskStyle}
|
||||||
expandableBadgeStylesheet.secondaryLabel,
|
|
||||||
expandableBadgeStylesheet.shimmerText,
|
|
||||||
shimmerSecondaryStyle,
|
|
||||||
]}
|
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
{secondaryLabel}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
{secondaryLabel ? (
|
||||||
<View style={expandableBadgeStylesheet.spacer} />
|
<Text
|
||||||
)}
|
style={nativeSecondaryMaskStyle}
|
||||||
</View>
|
numberOfLines={1}
|
||||||
) : null}
|
>
|
||||||
{isNativeShimmer ? (
|
{secondaryLabel}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<View style={expandableBadgeStylesheet.spacer} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
>
|
||||||
<View
|
<View
|
||||||
style={expandableBadgeStylesheet.shimmerOverlay}
|
style={nativeShimmerTrackStyle}
|
||||||
pointerEvents="none"
|
|
||||||
>
|
>
|
||||||
<MaskedView
|
<Animated.View style={nativeShimmerPeakCombinedStyle}>
|
||||||
style={[
|
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
||||||
expandableBadgeStylesheet.nativeShimmerTrack,
|
<Defs>
|
||||||
{ width: labelRowWidth, height: labelRowHeight },
|
<SvgLinearGradient
|
||||||
]}
|
id={nativeGradientIdRef.current}
|
||||||
maskElement={
|
x1="0%"
|
||||||
<View
|
y1="0%"
|
||||||
style={[
|
x2="100%"
|
||||||
expandableBadgeStylesheet.shimmerMaskRow,
|
y2="0%"
|
||||||
{ width: labelRowWidth, height: labelRowHeight },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
expandableBadgeStylesheet.label,
|
|
||||||
{ color: "#000000", opacity: 1 },
|
|
||||||
]}
|
|
||||||
numberOfLines={1}
|
|
||||||
>
|
>
|
||||||
{label}
|
<Stop offset="0%" stopColor="#ffffff" stopOpacity={0} />
|
||||||
</Text>
|
<Stop offset="50%" stopColor="#ffffff" stopOpacity={1} />
|
||||||
{secondaryLabel ? (
|
<Stop offset="100%" stopColor="#ffffff" stopOpacity={0} />
|
||||||
<Text
|
</SvgLinearGradient>
|
||||||
style={[
|
</Defs>
|
||||||
expandableBadgeStylesheet.secondaryLabel,
|
<Rect
|
||||||
{ color: "#000000", opacity: 1 },
|
x="0"
|
||||||
]}
|
y="0"
|
||||||
numberOfLines={1}
|
width="100%"
|
||||||
>
|
height="100%"
|
||||||
{secondaryLabel}
|
fill={`url(#${nativeGradientIdRef.current})`}
|
||||||
</Text>
|
/>
|
||||||
) : (
|
</Svg>
|
||||||
<View style={expandableBadgeStylesheet.spacer} />
|
</Animated.View>
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
expandableBadgeStylesheet.nativeShimmerTrack,
|
|
||||||
{ width: labelRowWidth, height: labelRowHeight },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Animated.View
|
|
||||||
style={[
|
|
||||||
expandableBadgeStylesheet.nativeShimmerPeak,
|
|
||||||
nativeShimmerPeakStyle,
|
|
||||||
{ width: nativeShimmerPeakWidth, height: labelRowHeight },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
|
||||||
<Defs>
|
|
||||||
<SvgLinearGradient
|
|
||||||
id={nativeGradientIdRef.current}
|
|
||||||
x1="0%"
|
|
||||||
y1="0%"
|
|
||||||
x2="100%"
|
|
||||||
y2="0%"
|
|
||||||
>
|
|
||||||
<Stop offset="0%" stopColor="#ffffff" stopOpacity={0} />
|
|
||||||
<Stop offset="50%" stopColor="#ffffff" stopOpacity={1} />
|
|
||||||
<Stop offset="100%" stopColor="#ffffff" stopOpacity={0} />
|
|
||||||
</SvgLinearGradient>
|
|
||||||
</Defs>
|
|
||||||
<Rect
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
fill={`url(#${nativeGradientIdRef.current})`}
|
|
||||||
/>
|
|
||||||
</Svg>
|
|
||||||
</Animated.View>
|
|
||||||
</View>
|
|
||||||
</MaskedView>
|
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
</MaskedView>
|
||||||
</View>
|
</View>
|
||||||
{isInteractive && hovered ? (
|
) : null}
|
||||||
<ChevronRight
|
</View>
|
||||||
size={14}
|
{isInteractive && isHovered ? (
|
||||||
color={theme.colors.foregroundMuted}
|
<ChevronRight
|
||||||
style={[
|
size={14}
|
||||||
expandableBadgeStylesheet.chevron,
|
color={theme.colors.foregroundMuted}
|
||||||
{ transform: [{ rotate: isExpanded ? "90deg" : "0deg" }] },
|
style={chevronStyle}
|
||||||
]}
|
/>
|
||||||
/>
|
) : null}
|
||||||
) : null}
|
</View>
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{detailContent ? (
|
{detailContent ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
outline: {
|
outline: {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.borderAccent,
|
||||||
},
|
},
|
||||||
ghost: {
|
ghost: {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
@@ -59,7 +59,7 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
text: {
|
text: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
fontWeight: theme.fontWeight.medium,
|
fontWeight: theme.fontWeight.normal,
|
||||||
},
|
},
|
||||||
textDefault: {
|
textDefault: {
|
||||||
color: theme.colors.palette.white,
|
color: theme.colors.palette.white,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function DesktopPermissionRow({
|
|||||||
<Text style={styles.permissionStatusText}>Granted</Text>
|
<Text style={styles.permissionStatusText}>Granted</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="secondary" size="sm" onPress={onRequest} disabled={isRequesting}>
|
<Button variant="outline" size="sm" onPress={onRequest} disabled={isRequesting}>
|
||||||
{isRequesting ? "Requesting..." : "Request"}
|
{isRequesting ? "Requesting..." : "Request"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { View, Text, Pressable } from "react-native";
|
import { View, Text } from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { RotateCw } from "lucide-react-native";
|
import { RotateCw } from "lucide-react-native";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { DesktopPermissionRow } from "@/desktop/components/desktop-permission-row";
|
import { DesktopPermissionRow } from "@/desktop/components/desktop-permission-row";
|
||||||
import { useDesktopPermissions } from "@/desktop/permissions/use-desktop-permissions";
|
import { useDesktopPermissions } from "@/desktop/permissions/use-desktop-permissions";
|
||||||
|
import { settingsStyles } from "@/styles/settings";
|
||||||
|
|
||||||
export function DesktopPermissionsSection() {
|
export function DesktopPermissionsSection() {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
@@ -22,26 +24,23 @@ export function DesktopPermissionsSection() {
|
|||||||
const isBusy = isRefreshing || requestingPermission !== null;
|
const isBusy = isRefreshing || requestingPermission !== null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.section}>
|
<View style={settingsStyles.section}>
|
||||||
<View style={styles.permissionSectionHeader}>
|
<View style={styles.permissionSectionHeader}>
|
||||||
<Text style={styles.sectionTitle}>Desktop Permissions</Text>
|
<Text style={settingsStyles.sectionTitle}>Desktop Permissions</Text>
|
||||||
<Pressable
|
<Button
|
||||||
style={({ pressed }) => [
|
variant="ghost"
|
||||||
styles.permissionRefreshButton,
|
size="sm"
|
||||||
isBusy && styles.permissionRefreshButtonDisabled,
|
leftIcon={<RotateCw size={theme.iconSize.md} color={theme.colors.foregroundMuted} />}
|
||||||
pressed && { opacity: 0.85 },
|
|
||||||
]}
|
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
void refreshPermissions();
|
void refreshPermissions();
|
||||||
}}
|
}}
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel="Refresh desktop permissions"
|
accessibilityLabel="Refresh desktop permissions"
|
||||||
>
|
>
|
||||||
<RotateCw size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
{isRefreshing ? "Refreshing..." : "Refresh"}
|
||||||
</Pressable>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.audioCard}>
|
<View style={settingsStyles.card}>
|
||||||
<DesktopPermissionRow
|
<DesktopPermissionRow
|
||||||
title="Notifications"
|
title="Notifications"
|
||||||
status={snapshot?.notifications ?? null}
|
status={snapshot?.notifications ?? null}
|
||||||
@@ -65,16 +64,6 @@ export function DesktopPermissionsSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
section: {
|
|
||||||
marginBottom: theme.spacing[6],
|
|
||||||
},
|
|
||||||
sectionTitle: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.normal,
|
|
||||||
marginBottom: 0,
|
|
||||||
marginLeft: theme.spacing[1],
|
|
||||||
},
|
|
||||||
permissionSectionHeader: {
|
permissionSectionHeader: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -82,24 +71,4 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
marginBottom: theme.spacing[3],
|
marginBottom: theme.spacing[3],
|
||||||
},
|
},
|
||||||
permissionRefreshButton: {
|
|
||||||
width: 34,
|
|
||||||
height: 34,
|
|
||||||
borderRadius: theme.borderRadius.md,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
permissionRefreshButtonDisabled: {
|
|
||||||
opacity: theme.opacity[50],
|
|
||||||
},
|
|
||||||
audioCard: {
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
overflow: "hidden",
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { ActivityIndicator, Alert, Image, Pressable, Text, View } from "react-na
|
|||||||
import * as Clipboard from "expo-clipboard";
|
import * as Clipboard from "expo-clipboard";
|
||||||
import * as QRCode from "qrcode";
|
import * as QRCode from "qrcode";
|
||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import { StyleSheet } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { ArrowUpRight } from "lucide-react-native";
|
import { settingsStyles } from "@/styles/settings";
|
||||||
|
import { ArrowUpRight, Play, Pause, RotateCw, Terminal, Copy, FileText, Smartphone } from "lucide-react-native";
|
||||||
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useAppSettings } from "@/hooks/use-settings";
|
import { useAppSettings } from "@/hooks/use-settings";
|
||||||
@@ -35,6 +36,7 @@ export interface LocalDaemonSectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
||||||
|
const { theme } = useUnistyles();
|
||||||
const showSection = shouldUseManagedDesktopDaemon();
|
const showSection = shouldUseManagedDesktopDaemon();
|
||||||
const { settings, updateSettings } = useAppSettings();
|
const { settings, updateSettings } = useAppSettings();
|
||||||
const [managedStatus, setManagedStatus] = useState<ManagedDaemonStatus | null>(null);
|
const [managedStatus, setManagedStatus] = useState<ManagedDaemonStatus | null>(null);
|
||||||
@@ -331,9 +333,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.section}>
|
<View style={settingsStyles.section}>
|
||||||
<View style={styles.sectionHeader}>
|
<View style={styles.sectionHeader}>
|
||||||
<Text style={styles.sectionTitle}>Built-in daemon</Text>
|
<Text style={settingsStyles.sectionTitle}>Built-in daemon</Text>
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityRole="link"
|
accessibilityRole="link"
|
||||||
onPress={() => void openExternalUrl(ADVANCED_DAEMON_SETTINGS_URL)}
|
onPress={() => void openExternalUrl(ADVANCED_DAEMON_SETTINGS_URL)}
|
||||||
@@ -343,7 +345,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
<ArrowUpRight size={14} color={styles.sectionLinkText.color} />
|
<ArrowUpRight size={14} color={styles.sectionLinkText.color} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.card}>
|
<View style={settingsStyles.card}>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<View style={styles.rowContent}>
|
<View style={styles.rowContent}>
|
||||||
<Text style={styles.rowTitle}>Status</Text>
|
<Text style={styles.rowTitle}>Status</Text>
|
||||||
@@ -364,9 +366,11 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
style={styles.primaryActionButton}
|
leftIcon={isDaemonManagementPaused
|
||||||
|
? <Play size={theme.iconSize.sm} color={theme.colors.foreground} />
|
||||||
|
: <Pause size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||||
onPress={handleToggleDaemonManagement}
|
onPress={handleToggleDaemonManagement}
|
||||||
disabled={isUpdatingDaemonManagement}
|
disabled={isUpdatingDaemonManagement}
|
||||||
>
|
>
|
||||||
@@ -388,9 +392,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
style={styles.primaryActionButton}
|
leftIcon={<RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||||
onPress={handleUpdateLocalDaemon}
|
onPress={handleUpdateLocalDaemon}
|
||||||
disabled={isRestartingDaemon}
|
disabled={isRestartingDaemon}
|
||||||
>
|
>
|
||||||
@@ -410,9 +414,9 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
{cliStatusMessage ? <Text style={styles.statusText}>{cliStatusMessage}</Text> : null}
|
{cliStatusMessage ? <Text style={styles.statusText}>{cliStatusMessage}</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
style={styles.secondaryActionButton}
|
leftIcon={<Terminal size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||||
onPress={handleToggleCliShim}
|
onPress={handleToggleCliShim}
|
||||||
disabled={isInstallingCli}
|
disabled={isInstallingCli}
|
||||||
>
|
>
|
||||||
@@ -434,13 +438,14 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.actionGroup}>
|
<View style={styles.actionGroup}>
|
||||||
{(managedLogs?.logPath ?? managedStatus?.logPath) ? (
|
{(managedLogs?.logPath ?? managedStatus?.logPath) ? (
|
||||||
<Button variant="secondary" size="sm" onPress={handleCopyLogPath}>
|
<Button variant="outline" size="sm" leftIcon={<Copy size={theme.iconSize.sm} color={theme.colors.foreground} />} onPress={handleCopyLogPath}>
|
||||||
Copy path
|
Copy path
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
leftIcon={<FileText size={theme.iconSize.sm} color={theme.colors.foreground} />}
|
||||||
onPress={handleOpenLogs}
|
onPress={handleOpenLogs}
|
||||||
disabled={!managedLogs}
|
disabled={!managedLogs}
|
||||||
>
|
>
|
||||||
@@ -455,7 +460,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
Connect your phone to this computer.
|
Connect your phone to this computer.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Button variant="secondary" size="sm" style={styles.secondaryActionButton} onPress={handleOpenPairingModal}>
|
<Button variant="outline" size="sm" leftIcon={<Smartphone size={theme.iconSize.sm} color={theme.colors.foreground} />} onPress={handleOpenPairingModal}>
|
||||||
Pair device
|
Pair device
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
@@ -488,7 +493,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
|
|||||||
{cliInstallInstructions?.commands ?? ""}
|
{cliInstallInstructions?.commands ?? ""}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.modalActions}>
|
<View style={styles.modalActions}>
|
||||||
<Button variant="secondary" size="sm" onPress={() => setIsCliInstallModalOpen(false)}>
|
<Button variant="outline" size="sm" onPress={() => setIsCliInstallModalOpen(false)}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" onPress={handleCopyCliInstallCommands}>
|
<Button size="sm" onPress={handleCopyCliInstallCommands}>
|
||||||
@@ -627,7 +632,7 @@ function PairingOfferDialogContent(input: {
|
|||||||
{pairingOffer.url}
|
{pairingOffer.url}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.modalActions}>
|
<View style={styles.modalActions}>
|
||||||
<Button variant="secondary" size="sm" onPress={onCopyLink}>
|
<Button variant="outline" size="sm" onPress={onCopyLink}>
|
||||||
Copy link
|
Copy link
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
@@ -636,9 +641,6 @@ function PairingOfferDialogContent(input: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
section: {
|
|
||||||
marginBottom: theme.spacing[6],
|
|
||||||
},
|
|
||||||
sectionHeader: {
|
sectionHeader: {
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -646,11 +648,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
marginBottom: theme.spacing[3],
|
marginBottom: theme.spacing[3],
|
||||||
marginLeft: theme.spacing[1],
|
marginLeft: theme.spacing[1],
|
||||||
},
|
},
|
||||||
sectionTitle: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.normal,
|
|
||||||
},
|
|
||||||
sectionLink: {
|
sectionLink: {
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -660,13 +657,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
},
|
},
|
||||||
card: {
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
overflow: "hidden",
|
|
||||||
},
|
|
||||||
row: {
|
row: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -692,12 +682,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "flex-end",
|
alignItems: "flex-end",
|
||||||
gap: 2,
|
gap: 2,
|
||||||
},
|
},
|
||||||
primaryActionButton: {
|
|
||||||
minWidth: 124,
|
|
||||||
},
|
|
||||||
secondaryActionButton: {
|
|
||||||
minWidth: 112,
|
|
||||||
},
|
|
||||||
rowTitle: {
|
rowTitle: {
|
||||||
color: theme.colors.foreground,
|
color: theme.colors.foreground,
|
||||||
fontSize: theme.fontSize.base,
|
fontSize: theme.fontSize.base,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { LocalDaemonSection } from "@/desktop/components/desktop-updates-section
|
|||||||
import { useDesktopAppUpdater } from "@/desktop/updates/use-desktop-app-updater";
|
import { useDesktopAppUpdater } from "@/desktop/updates/use-desktop-app-updater";
|
||||||
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates";
|
||||||
import { resolveAppVersion } from "@/utils/app-version";
|
import { resolveAppVersion } from "@/utils/app-version";
|
||||||
|
import { settingsStyles } from "@/styles/settings";
|
||||||
|
|
||||||
const delay = (ms: number) =>
|
const delay = (ms: number) =>
|
||||||
new Promise<void>((resolve) => {
|
new Promise<void>((resolve) => {
|
||||||
@@ -112,16 +113,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
maxWidth: 720,
|
maxWidth: 720,
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
},
|
},
|
||||||
section: {
|
|
||||||
marginBottom: theme.spacing[6],
|
|
||||||
},
|
|
||||||
sectionTitle: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.normal,
|
|
||||||
marginBottom: theme.spacing[3],
|
|
||||||
marginLeft: theme.spacing[1],
|
|
||||||
},
|
|
||||||
label: {
|
label: {
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
@@ -139,10 +130,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
// Host card styles
|
// Host card styles
|
||||||
hostCard: {
|
hostCard: {
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
marginBottom: theme.spacing[3],
|
marginBottom: theme.spacing[3],
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
@@ -286,10 +273,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
// Add/Edit form
|
// Add/Edit form
|
||||||
formCard: {
|
formCard: {
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
padding: theme.spacing[4],
|
padding: theme.spacing[4],
|
||||||
marginBottom: theme.spacing[3],
|
marginBottom: theme.spacing[3],
|
||||||
gap: theme.spacing[4],
|
gap: theme.spacing[4],
|
||||||
@@ -328,10 +311,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
// Audio settings card
|
// Audio settings card
|
||||||
audioCard: {
|
audioCard: {
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
audioRow: {
|
audioRow: {
|
||||||
@@ -375,10 +354,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
// Empty state
|
// Empty state
|
||||||
emptyCard: {
|
emptyCard: {
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
padding: theme.spacing[4],
|
padding: theme.spacing[4],
|
||||||
marginBottom: theme.spacing[3],
|
marginBottom: theme.spacing[3],
|
||||||
},
|
},
|
||||||
@@ -389,10 +364,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
// Dev section
|
// Dev section
|
||||||
devCard: {
|
devCard: {
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.lg,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.colors.border,
|
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
devButton: {
|
devButton: {
|
||||||
@@ -501,7 +472,7 @@ function DesktopAppUpdateRow() {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.aboutUpdateActions}>
|
<View style={styles.aboutUpdateActions}>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onPress={handleCheckForUpdates}
|
onPress={handleCheckForUpdates}
|
||||||
disabled={isChecking || isInstalling}
|
disabled={isChecking || isInstalling}
|
||||||
@@ -722,11 +693,11 @@ export default function SettingsScreen() {
|
|||||||
<ScrollView style={styles.scrollView} contentContainerStyle={{ paddingBottom: insets.bottom }}>
|
<ScrollView style={styles.scrollView} contentContainerStyle={{ paddingBottom: insets.bottom }}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{/* Host Management */}
|
{/* Host Management */}
|
||||||
<View style={styles.section}>
|
<View style={settingsStyles.section}>
|
||||||
<Text style={styles.sectionTitle}>Hosts</Text>
|
<Text style={settingsStyles.sectionTitle}>Hosts</Text>
|
||||||
|
|
||||||
{daemons.length === 0 ? (
|
{daemons.length === 0 ? (
|
||||||
<View style={styles.emptyCard}>
|
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||||
<Text style={styles.emptyText}>No hosts configured</Text>
|
<Text style={styles.emptyText}>No hosts configured</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@@ -878,9 +849,9 @@ export default function SettingsScreen() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Appearance */}
|
{/* Appearance */}
|
||||||
<View style={styles.section}>
|
<View style={settingsStyles.section}>
|
||||||
<Text style={styles.sectionTitle}>Appearance</Text>
|
<Text style={settingsStyles.sectionTitle}>Appearance</Text>
|
||||||
<View style={styles.audioCard}>
|
<View style={[settingsStyles.card, styles.audioCard]}>
|
||||||
<View style={styles.audioRow}>
|
<View style={styles.audioRow}>
|
||||||
<View style={styles.audioRowContent}>
|
<View style={styles.audioRowContent}>
|
||||||
<Text style={styles.audioRowTitle}>Theme</Text>
|
<Text style={styles.audioRowTitle}>Theme</Text>
|
||||||
@@ -915,9 +886,9 @@ export default function SettingsScreen() {
|
|||||||
{isDesktop ? <LocalDaemonSection appVersion={appVersion} /> : null}
|
{isDesktop ? <LocalDaemonSection appVersion={appVersion} /> : null}
|
||||||
|
|
||||||
{/* About */}
|
{/* About */}
|
||||||
<View style={styles.section}>
|
<View style={settingsStyles.section}>
|
||||||
<Text style={styles.sectionTitle}>About</Text>
|
<Text style={settingsStyles.sectionTitle}>About</Text>
|
||||||
<View style={styles.audioCard}>
|
<View style={[settingsStyles.card, styles.audioCard]}>
|
||||||
<View style={styles.audioRow}>
|
<View style={styles.audioRow}>
|
||||||
<View style={styles.audioRowContent}>
|
<View style={styles.audioRowContent}>
|
||||||
<Text style={styles.audioRowTitle}>Version</Text>
|
<Text style={styles.audioRowTitle}>Version</Text>
|
||||||
@@ -1332,7 +1303,7 @@ function ConnectionRow({
|
|||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: theme.colors.foreground, fontSize: 12, flex: 1 }}>
|
<Text style={{ color: theme.colors.foreground, fontSize: 12, flex: 1 }}>
|
||||||
@@ -1400,7 +1371,7 @@ function DaemonCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={styles.hostCard}
|
style={[settingsStyles.card, styles.hostCard]}
|
||||||
testID={`daemon-card-${daemon.serverId}`}
|
testID={`daemon-card-${daemon.serverId}`}
|
||||||
>
|
>
|
||||||
<View style={styles.hostCardContent}>
|
<View style={styles.hostCardContent}>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { StyleSheet } from "react-native-unistyles";
|
||||||
|
|
||||||
|
export const settingsStyles = StyleSheet.create((theme) => ({
|
||||||
|
section: {
|
||||||
|
marginBottom: theme.spacing[6],
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
fontWeight: theme.fontWeight.normal,
|
||||||
|
marginBottom: theme.spacing[3],
|
||||||
|
marginLeft: theme.spacing[1],
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: theme.colors.surface1,
|
||||||
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user