| author | |
| committer | |
| log | 18dcc43ace0a7b0d93f2fd61907b2a4e27522936 |
| tree | 6e509c34ea3e4ba4163f621ad2531b289152d6e4 |
| parent | 767a5dbd00d0a2050745a2ae2e29050c5282b8c0 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 63 insertions(+), 56 deletions(-)
apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx+19-28| ... | @@ -10,7 +10,7 @@ import { useCallback, useState } from "react"; | ... | @@ -10,7 +10,7 @@ import { useCallback, useState } from "react"; |
| 10 | import { Platform, Alert, Pressable, View } from "react-native"; | 10 | import { Platform, Alert, Pressable, View } from "react-native"; |
| 11 | import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; | 11 | import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated"; |
| 12 | 12 | ||
| 13 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; | 13 | import { AppText as Text } from "../../components/AppText"; |
| 14 | import { EnvironmentMachineSymbol } from "../../components/EnvironmentMachineSymbol"; | 14 | import { EnvironmentMachineSymbol } from "../../components/EnvironmentMachineSymbol"; |
| 15 | import { MaterialButton } from "../../components/MaterialButton"; | 15 | import { MaterialButton } from "../../components/MaterialButton"; |
| 16 | import { MaterialIconButton } from "../../components/MaterialIconButton"; | 16 | import { MaterialIconButton } from "../../components/MaterialIconButton"; |
| ... | @@ -18,6 +18,7 @@ import { ThemedSwitch } from "../../components/ThemedSwitch"; | ... | @@ -18,6 +18,7 @@ import { ThemedSwitch } from "../../components/ThemedSwitch"; |
| 18 | import { cn } from "../../lib/cn"; | 18 | import { cn } from "../../lib/cn"; |
| 19 | import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-types"; | 19 | import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-types"; |
| 20 | import { serverEnvironment } from "../../state/server"; | 20 | import { serverEnvironment } from "../../state/server"; |
| 21 | import { ConnectionFormField } from "./ConnectionFormField"; | ||
| 21 | import { ConnectionStatusDot } from "./ConnectionStatusDot"; | 22 | import { ConnectionStatusDot } from "./ConnectionStatusDot"; |
| 22 | 23 | ||
| 23 | function connectionStatusLabel(environment: ConnectedEnvironmentSummary): string | null { | 24 | function connectionStatusLabel(environment: ConnectedEnvironmentSummary): string | null { |
| ... | @@ -152,34 +153,24 @@ export function ConnectionEnvironmentRow(props: { | ... | @@ -152,34 +153,24 @@ export function ConnectionEnvironmentRow(props: { |
| 152 | </Text> | 153 | </Text> |
| 153 | ) : ( | 154 | ) : ( |
| 154 | <> | 155 | <> |
| 155 | <View className="gap-1.5"> | 156 | <ConnectionFormField |
| 156 | <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted"> | 157 | label="Label" |
| 157 | Label | 158 | autoCapitalize="words" |
| 158 | </Text> | 159 | autoCorrect={false} |
| 159 | <TextInput | 160 | placeholder="My MacBook" |
| 160 | autoCapitalize="words" | 161 | value={label} |
| 161 | autoCorrect={false} | 162 | onChangeText={setLabel} |
| 162 | placeholder="My MacBook" | 163 | /> |
| 163 | value={label} | ||
| 164 | onChangeText={setLabel} | ||
| 165 | className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground" | ||
| 166 | /> | ||
| 167 | </View> | ||
| 168 | 164 | ||
| 169 | <View className="gap-1.5"> | 165 | <ConnectionFormField |
| 170 | <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted"> | 166 | label="URL" |
| 171 | URL | 167 | autoCapitalize="none" |
| 172 | </Text> | 168 | autoCorrect={false} |
| 173 | <TextInput | 169 | keyboardType="url" |
| 174 | autoCapitalize="none" | 170 | placeholder="192.168.1.100:8080" |
| 175 | autoCorrect={false} | 171 | value={url} |
| 176 | keyboardType="url" | 172 | onChangeText={setUrl} |
| 177 | placeholder="192.168.1.100:8080" | 173 | /> |
| 178 | value={url} | ||
| 179 | onChangeText={setUrl} | ||
| 180 | className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground" | ||
| 181 | /> | ||
| 182 | </View> | ||
| 183 | </> | 174 | </> |
| 184 | )} | 175 | )} |
| 185 | 176 |
apps/mobile/src/features/connection/ConnectionFormField.tsx created+25| ... | @@ -0,0 +1,25 @@ | ||
| 1 | import { View } from "react-native"; | ||
| 2 | |||
| 3 | import { AppText, AppTextInput, type AppTextInputProps } from "../../components/AppText"; | ||
| 4 | import { cn } from "../../lib/cn"; | ||
| 5 | |||
| 6 | type ConnectionFormFieldProps = Omit<AppTextInputProps, "className"> & { | ||
| 7 | readonly label: string; | ||
| 8 | readonly className?: string; | ||
| 9 | }; | ||
| 10 | |||
| 11 | /** Labeled connection input with a native wrapper retained inside form sheets. */ | ||
| 12 | export function ConnectionFormField({ label, className, ...inputProps }: ConnectionFormFieldProps) { | ||
| 13 | return ( | ||
| 14 | <View collapsable={false} className={cn("gap-1.5", className)}> | ||
| 15 | <AppText className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted"> | ||
| 16 | {label} | ||
| 17 | </AppText> | ||
| 18 | <AppTextInput | ||
| 19 | accessibilityLabel={label} | ||
| 20 | {...inputProps} | ||
| 21 | className="rounded-[14px] px-4 py-3.5" | ||
| 22 | /> | ||
| 23 | </View> | ||
| 24 | ); | ||
| 25 | } | ||
apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsx+19-28| ... | @@ -13,8 +13,9 @@ import { Alert, Linking, Platform, View } from "react-native"; | ... | @@ -13,8 +13,9 @@ import { Alert, Linking, Platform, View } from "react-native"; |
| 13 | import { useSafeAreaInsets } from "react-native-safe-area-context"; | 13 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 14 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; | 14 | import { useUniwindTheme } from "../../lib/useUniwindTheme"; |
| 15 | import { SettingsScreen } from "../settings/components/SettingsScreen"; | 15 | import { SettingsScreen } from "../settings/components/SettingsScreen"; |
| 16 | import { AppText as Text, AppTextInput as TextInput } from "../../components/AppText"; | 16 | import { AppText as Text } from "../../components/AppText"; |
| 17 | import { ErrorBanner } from "../../components/ErrorBanner"; | 17 | import { ErrorBanner } from "../../components/ErrorBanner"; |
| 18 | import { ConnectionFormField } from "./ConnectionFormField"; | ||
| 18 | import { ConnectionSheetButton } from "./ConnectionSheetButton"; | 19 | import { ConnectionSheetButton } from "./ConnectionSheetButton"; |
| 19 | import { buildPairingUrl, extractPairingUrlFromQrPayload, parsePairingUrl } from "./pairing"; | 20 | import { buildPairingUrl, extractPairingUrlFromQrPayload, parsePairingUrl } from "./pairing"; |
| 20 | import { useRemoteConnections } from "../../state/use-remote-environment-registry"; | 21 | import { useRemoteConnections } from "../../state/use-remote-environment-registry"; |
| ... | @@ -262,34 +263,24 @@ export function ConnectionsNewRouteScreen({ | ... | @@ -262,34 +263,24 @@ export function ConnectionsNewRouteScreen({ |
| 262 | ) | 263 | ) |
| 263 | ) : ( | 264 | ) : ( |
| 264 | <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4"> | 265 | <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4"> |
| 265 | <View collapsable={false} className="gap-1.5"> | 266 | <ConnectionFormField |
| 266 | <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted"> | 267 | label="Host" |
| 267 | Host | 268 | autoCapitalize="none" |
| 268 | </Text> | 269 | autoCorrect={false} |
| 269 | <TextInput | 270 | keyboardType="url" |
| 270 | autoCapitalize="none" | 271 | placeholder="192.168.1.100:8080" |
| 271 | autoCorrect={false} | 272 | value={hostInput} |
| 272 | keyboardType="url" | 273 | onChangeText={handleHostChange} |
| 273 | placeholder="192.168.1.100:8080" | 274 | /> |
| 274 | value={hostInput} | ||
| 275 | onChangeText={handleHostChange} | ||
| 276 | className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground" | ||
| 277 | /> | ||
| 278 | </View> | ||
| 279 | 275 | ||
| 280 | <View collapsable={false} className="gap-1.5"> | 276 | <ConnectionFormField |
| 281 | <Text className="text-2xs font-t3-bold tracking-[0.8px] uppercase text-foreground-muted"> | 277 | label="Pairing code" |
| 282 | Pairing code | 278 | autoCapitalize="none" |
| 283 | </Text> | 279 | autoCorrect={false} |
| 284 | <TextInput | 280 | placeholder="abc-123-xyz" |
| 285 | autoCapitalize="none" | 281 | value={codeInput} |
| 286 | autoCorrect={false} | 282 | onChangeText={handleCodeChange} |
| 287 | placeholder="abc-123-xyz" | 283 | /> |
| 288 | value={codeInput} | ||
| 289 | onChangeText={handleCodeChange} | ||
| 290 | className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground" | ||
| 291 | /> | ||
| 292 | </View> | ||
| 293 | 284 | ||
| 294 | {pairingConnectionError ? <ErrorBanner message={pairingConnectionError} /> : null} | 285 | {pairingConnectionError ? <ErrorBanner message={pairingConnectionError} /> : null} |
| 295 | 286 |