Add shared trigger components and horizontal desktop workspace arguments
- Add DropdownTrigger and ComboboxTrigger with an overridable chevron slot and a 1 px translateY caret nudge for consistent vertical alignment. - Convert text-based dropdown/combobox triggers across model selector, agent controls, mode control, settings, and new workspace to use the shared components. - Lay out New Workspace arguments horizontally on desktop without labels while keeping the mobile vertical stack; align the project icon left edge with the title.
This commit is contained in:
@@ -12,14 +12,8 @@ import { BottomSheetFlatList } from "@gorhom/bottom-sheet";
|
|||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||||
import { isNative, isWeb as platformIsWeb } from "@/constants/platform";
|
import { isNative, isWeb as platformIsWeb } from "@/constants/platform";
|
||||||
import {
|
import { AlertTriangle, ChevronRight, Search, Settings, Star } from "lucide-react-native";
|
||||||
AlertTriangle,
|
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||||
ChevronDown,
|
|
||||||
ChevronRight,
|
|
||||||
Search,
|
|
||||||
Settings,
|
|
||||||
Star,
|
|
||||||
} from "lucide-react-native";
|
|
||||||
import type { AgentProvider } from "@getpaseo/protocol/agent-types";
|
import type { AgentProvider } from "@getpaseo/protocol/agent-types";
|
||||||
import type { SheetHeader } from "@/components/adaptive-modal-sheet";
|
import type { SheetHeader } from "@/components/adaptive-modal-sheet";
|
||||||
import { useProviderSettingsStore } from "@/stores/provider-settings-store";
|
import { useProviderSettingsStore } from "@/stores/provider-settings-store";
|
||||||
@@ -784,35 +778,43 @@ export function CombinedModelSelector({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Pressable
|
{renderTrigger ? (
|
||||||
ref={anchorRef}
|
<Pressable
|
||||||
collapsable={false}
|
ref={anchorRef}
|
||||||
disabled={disabled}
|
collapsable={false}
|
||||||
onPress={handleTriggerPress}
|
disabled={disabled}
|
||||||
style={triggerStyle}
|
onPress={handleTriggerPress}
|
||||||
accessibilityRole="button"
|
style={triggerStyle}
|
||||||
accessibilityLabel={t("modelSelector.selectedModel", { model: selectedModelLabel })}
|
accessibilityRole="button"
|
||||||
testID="combined-model-selector"
|
accessibilityLabel={t("modelSelector.selectedModel", { model: selectedModelLabel })}
|
||||||
>
|
testID="combined-model-selector"
|
||||||
{renderTrigger ? (
|
>
|
||||||
renderTrigger({
|
{renderTrigger({
|
||||||
selectedModelLabel: triggerLabel,
|
selectedModelLabel: triggerLabel,
|
||||||
onPress: handleTriggerPress,
|
onPress: handleTriggerPress,
|
||||||
disabled,
|
disabled,
|
||||||
isOpen,
|
isOpen,
|
||||||
})
|
})}
|
||||||
) : (
|
</Pressable>
|
||||||
<>
|
) : (
|
||||||
{ProviderIcon ? (
|
<ComboboxTrigger
|
||||||
<ProviderIcon size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
ref={anchorRef}
|
||||||
) : null}
|
collapsable={false}
|
||||||
<Text style={styles.triggerText} numberOfLines={1} ellipsizeMode="tail">
|
disabled={disabled}
|
||||||
{triggerLabel}
|
onPress={handleTriggerPress}
|
||||||
</Text>
|
style={triggerStyle}
|
||||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
accessibilityRole="button"
|
||||||
</>
|
accessibilityLabel={t("modelSelector.selectedModel", { model: selectedModelLabel })}
|
||||||
)}
|
testID="combined-model-selector"
|
||||||
</Pressable>
|
>
|
||||||
|
{ProviderIcon ? (
|
||||||
|
<ProviderIcon size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
|
) : null}
|
||||||
|
<Text style={styles.triggerText} numberOfLines={1} ellipsizeMode="tail">
|
||||||
|
{triggerLabel}
|
||||||
|
</Text>
|
||||||
|
</ComboboxTrigger>
|
||||||
|
)}
|
||||||
<Combobox
|
<Combobox
|
||||||
options={EMPTY_COMBOBOX_OPTIONS}
|
options={EMPTY_COMBOBOX_OPTIONS}
|
||||||
value=""
|
value=""
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { forwardRef, useCallback, type ReactElement, type ReactNode } from "react";
|
||||||
|
import {
|
||||||
|
Pressable,
|
||||||
|
View,
|
||||||
|
type PressableProps,
|
||||||
|
type PressableStateCallbackType,
|
||||||
|
type ViewStyle,
|
||||||
|
type StyleProp,
|
||||||
|
} from "react-native";
|
||||||
|
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||||
|
import { ChevronDown } from "lucide-react-native";
|
||||||
|
import { ICON_SIZE, type Theme } from "@/styles/theme";
|
||||||
|
|
||||||
|
const ThemedChevronDown = withUnistyles(ChevronDown);
|
||||||
|
|
||||||
|
const chevronColorMapping = (theme: Theme) => ({
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
});
|
||||||
|
|
||||||
|
interface TriggerState {
|
||||||
|
pressed: boolean;
|
||||||
|
hovered: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TriggerStyleProp = StyleProp<ViewStyle> | ((state: TriggerState) => StyleProp<ViewStyle>);
|
||||||
|
|
||||||
|
interface ComboboxTriggerProps extends Omit<PressableProps, "style" | "children"> {
|
||||||
|
style?: TriggerStyleProp;
|
||||||
|
children?: ReactNode;
|
||||||
|
chevron?: ReactNode | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ComboboxTrigger = forwardRef<View, ComboboxTriggerProps>(function ComboboxTrigger(
|
||||||
|
{ children, chevron, style, ...props },
|
||||||
|
ref,
|
||||||
|
): ReactElement {
|
||||||
|
const pressableStyle = useCallback(
|
||||||
|
({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => {
|
||||||
|
if (typeof style === "function") {
|
||||||
|
return style({ pressed, hovered });
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
[style],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable ref={ref} collapsable={false} style={pressableStyle} {...props}>
|
||||||
|
<View style={styles.row}>
|
||||||
|
{children}
|
||||||
|
{chevron !== null &&
|
||||||
|
(chevron ?? (
|
||||||
|
<View style={styles.chevronContainer}>
|
||||||
|
<ThemedChevronDown size={ICON_SIZE.sm} uniProps={chevronColorMapping} />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const styles = StyleSheet.create((theme) => ({
|
||||||
|
row: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[1],
|
||||||
|
},
|
||||||
|
chevronContainer: {
|
||||||
|
transform: [{ translateY: 1 }],
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -303,7 +303,7 @@ interface TriggerState {
|
|||||||
}
|
}
|
||||||
type TriggerStyleProp = StyleProp<ViewStyle> | ((state: TriggerState) => StyleProp<ViewStyle>);
|
type TriggerStyleProp = StyleProp<ViewStyle> | ((state: TriggerState) => StyleProp<ViewStyle>);
|
||||||
|
|
||||||
interface DropdownMenuTriggerProps extends Omit<PressableProps, "style" | "children"> {
|
export interface DropdownMenuTriggerProps extends Omit<PressableProps, "style" | "children"> {
|
||||||
style?: TriggerStyleProp;
|
style?: TriggerStyleProp;
|
||||||
children: ReactNode | ((state: TriggerState) => ReactNode);
|
children: ReactNode | ((state: TriggerState) => ReactNode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import type { ReactElement, ReactNode } from "react";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||||
|
import { ChevronDown } from "lucide-react-native";
|
||||||
|
import { ICON_SIZE, type Theme } from "@/styles/theme";
|
||||||
|
import { DropdownMenuTrigger, type DropdownMenuTriggerProps } from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
const ThemedChevronDown = withUnistyles(ChevronDown);
|
||||||
|
|
||||||
|
interface DropdownTriggerProps extends Omit<DropdownMenuTriggerProps, "children"> {
|
||||||
|
children?: ReactNode;
|
||||||
|
chevron?: ReactNode | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chevronColorMapping = (theme: Theme) => ({
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function DropdownTrigger({
|
||||||
|
children,
|
||||||
|
chevron,
|
||||||
|
...props
|
||||||
|
}: DropdownTriggerProps): ReactElement {
|
||||||
|
return (
|
||||||
|
<DropdownMenuTrigger {...props}>
|
||||||
|
<View style={styles.row}>
|
||||||
|
{children}
|
||||||
|
{chevron !== null &&
|
||||||
|
(chevron ?? (
|
||||||
|
<View style={styles.chevronContainer}>
|
||||||
|
<ThemedChevronDown size={ICON_SIZE.sm} uniProps={chevronColorMapping} />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create((theme) => ({
|
||||||
|
row: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[1],
|
||||||
|
},
|
||||||
|
chevronContainer: {
|
||||||
|
transform: [{ translateY: 1 }],
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -20,7 +20,9 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { Brain, ChevronDown, ListTodo, Settings2, ShieldCheck, Zap } from "lucide-react-native";
|
import { Brain, ListTodo, Settings2, ShieldCheck, Zap } from "lucide-react-native";
|
||||||
|
import { DropdownTrigger } from "@/components/ui/dropdown-trigger";
|
||||||
|
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||||
import { getProviderIcon } from "@/components/provider-icons";
|
import { getProviderIcon } from "@/components/provider-icons";
|
||||||
import { CombinedModelSelector } from "@/components/combined-model-selector";
|
import { CombinedModelSelector } from "@/components/combined-model-selector";
|
||||||
import {
|
import {
|
||||||
@@ -37,12 +39,7 @@ import {
|
|||||||
toggleFavoriteModel,
|
toggleFavoriteModel,
|
||||||
useFormPreferences,
|
useFormPreferences,
|
||||||
} from "@/hooks/use-form-preferences";
|
} from "@/hooks/use-form-preferences";
|
||||||
import {
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
||||||
import { DraftAgentModeControl, AgentModeControl } from "@/composer/agent-controls/mode-control";
|
import { DraftAgentModeControl, AgentModeControl } from "@/composer/agent-controls/mode-control";
|
||||||
import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet";
|
||||||
@@ -764,7 +761,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
<>
|
<>
|
||||||
{providerOptions && providerOptions.length > 0 ? (
|
{providerOptions && providerOptions.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<Pressable
|
<ComboboxTrigger
|
||||||
ref={providerAnchorRef}
|
ref={providerAnchorRef}
|
||||||
collapsable={false}
|
collapsable={false}
|
||||||
disabled={disabled || !canSelectProvider}
|
disabled={disabled || !canSelectProvider}
|
||||||
@@ -775,8 +772,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
testID="agent-provider-selector"
|
testID="agent-provider-selector"
|
||||||
>
|
>
|
||||||
<Text style={styles.modeBadgeText}>{displayProvider}</Text>
|
<Text style={styles.modeBadgeText}>{displayProvider}</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
</ComboboxTrigger>
|
||||||
</Pressable>
|
|
||||||
<Combobox
|
<Combobox
|
||||||
options={comboboxProviderOptions}
|
options={comboboxProviderOptions}
|
||||||
value={selectedProviderId ?? ""}
|
value={selectedProviderId ?? ""}
|
||||||
@@ -821,7 +817,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
<>
|
<>
|
||||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||||
<TooltipTrigger asChild triggerRefProp="ref">
|
<TooltipTrigger asChild triggerRefProp="ref">
|
||||||
<Pressable
|
<ComboboxTrigger
|
||||||
ref={thinkingAnchorRef}
|
ref={thinkingAnchorRef}
|
||||||
collapsable={false}
|
collapsable={false}
|
||||||
disabled={disabled || !canSelectThinking}
|
disabled={disabled || !canSelectThinking}
|
||||||
@@ -835,8 +831,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
>
|
>
|
||||||
<Brain size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
<Brain size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
<Text style={styles.modeBadgeText}>{displayThinking}</Text>
|
<Text style={styles.modeBadgeText}>{displayThinking}</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
</ComboboxTrigger>
|
||||||
</Pressable>
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top" align="center" offset={8}>
|
<TooltipContent side="top" align="center" offset={8}>
|
||||||
<Text style={styles.tooltipText}>{t(getAgentControlHintKey("thinking"))}</Text>
|
<Text style={styles.tooltipText}>{t(getAgentControlHintKey("thinking"))}</Text>
|
||||||
@@ -1173,7 +1168,7 @@ function DesktopFeatureItem({
|
|||||||
<DropdownMenu open={openSelector === featureSelector} onOpenChange={handleFeatureOpenChange}>
|
<DropdownMenu open={openSelector === featureSelector} onOpenChange={handleFeatureOpenChange}>
|
||||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||||
<TooltipTrigger asChild triggerRefProp="ref">
|
<TooltipTrigger asChild triggerRefProp="ref">
|
||||||
<DropdownMenuTrigger
|
<DropdownTrigger
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={selectPressableStyle}
|
style={selectPressableStyle}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
@@ -1182,8 +1177,7 @@ function DesktopFeatureItem({
|
|||||||
>
|
>
|
||||||
<FeatureIcon size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
<FeatureIcon size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
<Text style={styles.modeBadgeText}>{selectedOption?.label ?? feature.label}</Text>
|
<Text style={styles.modeBadgeText}>{selectedOption?.label ?? feature.label}</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
</DropdownTrigger>
|
||||||
</DropdownMenuTrigger>
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top" align="center" offset={8}>
|
<TooltipContent side="top" align="center" offset={8}>
|
||||||
<Text style={styles.tooltipText}>{getFeatureTooltip(feature)}</Text>
|
<Text style={styles.tooltipText}>{getFeatureTooltip(feature)}</Text>
|
||||||
@@ -1288,7 +1282,7 @@ function SheetFeatureItem({
|
|||||||
open={openSelector === featureSelector}
|
open={openSelector === featureSelector}
|
||||||
onOpenChange={handleFeatureOpenChange}
|
onOpenChange={handleFeatureOpenChange}
|
||||||
>
|
>
|
||||||
<DropdownMenuTrigger
|
<DropdownTrigger
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={togglePressableStyle}
|
style={togglePressableStyle}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
@@ -1296,8 +1290,7 @@ function SheetFeatureItem({
|
|||||||
testID={`agent-feature-${feature.id}`}
|
testID={`agent-feature-${feature.id}`}
|
||||||
>
|
>
|
||||||
<Text style={styles.sheetSelectText}>{selectedOption?.label ?? feature.label}</Text>
|
<Text style={styles.sheetSelectText}>{selectedOption?.label ?? feature.label}</Text>
|
||||||
<ChevronDown size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
</DropdownTrigger>
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent side="top" align="start">
|
<DropdownMenuContent side="top" align="start">
|
||||||
{feature.options.map((option) => (
|
{feature.options.map((option) => (
|
||||||
<FeatureOptionMenuItem
|
<FeatureOptionMenuItem
|
||||||
|
|||||||
@@ -8,18 +8,12 @@ import {
|
|||||||
type ReactElement,
|
type ReactElement,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Pressable, Text, View, type PressableStateCallbackType } from "react-native";
|
import { Text, View, type PressableStateCallbackType } from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { useStoreWithEqualityFn } from "zustand/traditional";
|
import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||||
import {
|
import { Bot, ShieldAlert, ShieldCheck, ShieldOff, ShieldQuestionMark } from "lucide-react-native";
|
||||||
Bot,
|
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||||
ChevronDown,
|
|
||||||
ShieldAlert,
|
|
||||||
ShieldCheck,
|
|
||||||
ShieldOff,
|
|
||||||
ShieldQuestionMark,
|
|
||||||
} from "lucide-react-native";
|
|
||||||
import { type SheetHeader } from "@/components/adaptive-modal-sheet";
|
import { type SheetHeader } from "@/components/adaptive-modal-sheet";
|
||||||
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
@@ -198,7 +192,7 @@ function AgentModeControlView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Pressable
|
<ComboboxTrigger
|
||||||
ref={anchorRef}
|
ref={anchorRef}
|
||||||
collapsable={false}
|
collapsable={false}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -212,8 +206,7 @@ function AgentModeControlView({
|
|||||||
>
|
>
|
||||||
{Icon ? <Icon size={theme.iconSize.md} color={iconColor} /> : null}
|
{Icon ? <Icon size={theme.iconSize.md} color={iconColor} /> : null}
|
||||||
<Text style={labelStyle}>{selectedModeLabel}</Text>
|
<Text style={labelStyle}>{selectedModeLabel}</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={iconColor} />
|
</ComboboxTrigger>
|
||||||
</Pressable>
|
|
||||||
<Combobox
|
<Combobox
|
||||||
options={options}
|
options={options}
|
||||||
value={selectedMode.id}
|
value={selectedMode.id}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { createNameId } from "mnemonic-id";
|
import { createNameId } from "mnemonic-id";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Check, ChevronDown, Folder, GitBranch, GitPullRequest, X } from "lucide-react-native";
|
import { Check, Folder, GitBranch, GitPullRequest, X } from "lucide-react-native";
|
||||||
import { Composer } from "@/composer";
|
import { Composer } from "@/composer";
|
||||||
import { DraftAgentModeControl } from "@/composer/agent-controls/mode-control";
|
import { DraftAgentModeControl } from "@/composer/agent-controls/mode-control";
|
||||||
import { splitComposerAttachmentsForSubmit } from "@/composer/attachments/submit";
|
import { splitComposerAttachmentsForSubmit } from "@/composer/attachments/submit";
|
||||||
@@ -16,6 +16,7 @@ import { FileDropZone } from "@/components/file-drop-zone";
|
|||||||
import { ProjectIconView } from "@/components/project-icon-view";
|
import { ProjectIconView } from "@/components/project-icon-view";
|
||||||
import { Combobox, ComboboxItem } from "@/components/ui/combobox";
|
import { Combobox, ComboboxItem } from "@/components/ui/combobox";
|
||||||
import type { ComboboxOption as ComboboxOptionType } from "@/components/ui/combobox";
|
import type { ComboboxOption as ComboboxOptionType } from "@/components/ui/combobox";
|
||||||
|
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
|
||||||
import { SidebarMenuToggle } from "@/components/headers/menu-header";
|
import { SidebarMenuToggle } from "@/components/headers/menu-header";
|
||||||
@@ -166,7 +167,6 @@ function RefPickerBadgeContent({
|
|||||||
<Text style={styles.badgeText} numberOfLines={1}>
|
<Text style={styles.badgeText} numberOfLines={1}>
|
||||||
{triggerLabel}
|
{triggerLabel}
|
||||||
</Text>
|
</Text>
|
||||||
<ChevronDown size={iconSize} color={iconColor} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ function RefPickerTrigger({
|
|||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild triggerRefProp="ref">
|
<TooltipTrigger asChild triggerRefProp="ref">
|
||||||
<Pressable
|
<ComboboxTrigger
|
||||||
ref={pickerAnchorRef}
|
ref={pickerAnchorRef}
|
||||||
testID="new-workspace-ref-picker-trigger"
|
testID="new-workspace-ref-picker-trigger"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
@@ -212,7 +212,7 @@ function RefPickerTrigger({
|
|||||||
iconColor={iconColor}
|
iconColor={iconColor}
|
||||||
iconSize={iconSize}
|
iconSize={iconSize}
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
</ComboboxTrigger>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top" align="center" offset={8}>
|
<TooltipContent side="top" align="center" offset={8}>
|
||||||
<Text style={styles.tooltipText}>{tooltipLabel}</Text>
|
<Text style={styles.tooltipText}>{tooltipLabel}</Text>
|
||||||
@@ -247,7 +247,7 @@ function ProjectPickerTrigger({
|
|||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild triggerRefProp="ref">
|
<TooltipTrigger asChild triggerRefProp="ref">
|
||||||
<Pressable
|
<ComboboxTrigger
|
||||||
ref={pickerAnchorRef}
|
ref={pickerAnchorRef}
|
||||||
testID="new-workspace-project-picker-trigger"
|
testID="new-workspace-project-picker-trigger"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
@@ -273,8 +273,7 @@ function ProjectPickerTrigger({
|
|||||||
<Text style={styles.badgeText} numberOfLines={1}>
|
<Text style={styles.badgeText} numberOfLines={1}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<ChevronDown size={iconSize} color={iconColor} />
|
</ComboboxTrigger>
|
||||||
</Pressable>
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top" align="center" offset={8}>
|
<TooltipContent side="top" align="center" offset={8}>
|
||||||
<Text style={styles.tooltipText}>Choose project</Text>
|
<Text style={styles.tooltipText}>Choose project</Text>
|
||||||
@@ -652,7 +651,7 @@ function IsolationPickerTrigger({
|
|||||||
iconSize: number;
|
iconSize: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<ComboboxTrigger
|
||||||
ref={pickerAnchorRef}
|
ref={pickerAnchorRef}
|
||||||
testID="workspace-create-backing-trigger"
|
testID="workspace-create-backing-trigger"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
@@ -671,21 +670,14 @@ function IsolationPickerTrigger({
|
|||||||
<Text style={styles.badgeText} numberOfLines={1}>
|
<Text style={styles.badgeText} numberOfLines={1}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<ChevronDown size={iconSize} color={iconColor} />
|
</ComboboxTrigger>
|
||||||
</Pressable>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each row aligns its label glyph with the screen heading glyph and lets the
|
// Wraps a single argument control in the mobile vertical stack. On desktop the
|
||||||
// label's natural width push the control. The label and control sit side by
|
// controls are laid out in one horizontal row, so no per-control wrapper is used.
|
||||||
// side with no held horizontal space.
|
function FormRow({ children }: { children: React.ReactNode }) {
|
||||||
function LabeledRow({ label, children }: { label: string; children: React.ReactNode }) {
|
return <View style={styles.row}>{children}</View>;
|
||||||
return (
|
|
||||||
<View style={styles.row}>
|
|
||||||
<Text style={styles.rowLabel}>{label}</Text>
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkspaceBackingState {
|
interface WorkspaceBackingState {
|
||||||
@@ -1533,155 +1525,167 @@ export function NewWorkspaceScreen({
|
|||||||
|
|
||||||
const backingTriggerLabel = backingLabel(t, effectiveBacking);
|
const backingTriggerLabel = backingLabel(t, effectiveBacking);
|
||||||
|
|
||||||
const formStack = useMemo(
|
const formStack = useMemo(() => {
|
||||||
() => (
|
const projectControl = (
|
||||||
<View testID="new-workspace-ref-picker-row" style={styles.formStack}>
|
<View>
|
||||||
<LabeledRow label={t("newWorkspace.fields.project")}>
|
<ProjectPickerTrigger
|
||||||
<View>
|
pickerAnchorRef={projectPickerAnchorRef}
|
||||||
<ProjectPickerTrigger
|
onPress={openProjectPicker}
|
||||||
pickerAnchorRef={projectPickerAnchorRef}
|
disabled={isPending || projectPickerOptions.length === 0}
|
||||||
onPress={openProjectPicker}
|
badgePressableStyle={badgePressableStyle}
|
||||||
disabled={isPending || projectPickerOptions.length === 0}
|
label={projectTriggerLabel}
|
||||||
badgePressableStyle={badgePressableStyle}
|
projectKey={selectedProject?.projectKey ?? null}
|
||||||
label={projectTriggerLabel}
|
iconDataUri={
|
||||||
projectKey={selectedProject?.projectKey ?? null}
|
selectedProject
|
||||||
iconDataUri={
|
? (projectIconDataByProjectKey.get(selectedProject.projectKey) ?? null)
|
||||||
selectedProject
|
: null
|
||||||
? (projectIconDataByProjectKey.get(selectedProject.projectKey) ?? null)
|
}
|
||||||
: null
|
iconColor={theme.colors.foregroundMuted}
|
||||||
}
|
iconSize={theme.iconSize.sm}
|
||||||
iconColor={theme.colors.foregroundMuted}
|
/>
|
||||||
iconSize={theme.iconSize.sm}
|
<Combobox
|
||||||
/>
|
options={projectPickerOptions}
|
||||||
<Combobox
|
value={selectedProjectOptionId}
|
||||||
options={projectPickerOptions}
|
onSelect={handleSelectProjectOption}
|
||||||
value={selectedProjectOptionId}
|
searchable
|
||||||
onSelect={handleSelectProjectOption}
|
searchPlaceholder="Search projects"
|
||||||
searchable
|
title="Project"
|
||||||
searchPlaceholder="Search projects"
|
open={projectPickerOpen}
|
||||||
title="Project"
|
onOpenChange={handleProjectPickerOpenChange}
|
||||||
open={projectPickerOpen}
|
desktopPlacement="bottom-start"
|
||||||
onOpenChange={handleProjectPickerOpenChange}
|
anchorRef={projectPickerAnchorRef}
|
||||||
desktopPlacement="bottom-start"
|
emptyText="No projects available."
|
||||||
anchorRef={projectPickerAnchorRef}
|
renderOption={renderProjectOption}
|
||||||
emptyText="No projects available."
|
/>
|
||||||
renderOption={renderProjectOption}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</LabeledRow>
|
|
||||||
{/* The Isolation row keeps its height for non-git projects so switching
|
|
||||||
projects never shifts the form; worktree backing is git-only, so a
|
|
||||||
non-git project renders an invisible spacer matching the trigger
|
|
||||||
height exactly. */}
|
|
||||||
{canCreateWorktree ? (
|
|
||||||
<LabeledRow label={t("newWorkspace.backing.label")}>
|
|
||||||
<View>
|
|
||||||
<IsolationPickerTrigger
|
|
||||||
pickerAnchorRef={backingPickerAnchorRef}
|
|
||||||
onPress={openBackingPicker}
|
|
||||||
disabled={isPending}
|
|
||||||
badgePressableStyle={badgePressableStyle}
|
|
||||||
backing={effectiveBacking}
|
|
||||||
label={backingTriggerLabel}
|
|
||||||
iconColor={theme.colors.foregroundMuted}
|
|
||||||
iconSize={theme.iconSize.sm}
|
|
||||||
/>
|
|
||||||
<Combobox
|
|
||||||
options={backingOptions}
|
|
||||||
value={effectiveBacking}
|
|
||||||
onSelect={handleSelectBackingOption}
|
|
||||||
title={t("newWorkspace.backing.label")}
|
|
||||||
open={backingPickerOpen}
|
|
||||||
onOpenChange={handleBackingPickerOpenChange}
|
|
||||||
desktopPlacement="bottom-start"
|
|
||||||
anchorRef={backingPickerAnchorRef}
|
|
||||||
renderOption={renderBackingOption}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</LabeledRow>
|
|
||||||
) : (
|
|
||||||
<View style={styles.baseSpacer} />
|
|
||||||
)}
|
|
||||||
{/* The Base row keeps its height so toggling Isolation never shifts the
|
|
||||||
form; on Local backing it renders an invisible spacer with no label
|
|
||||||
or control, matching the trigger height exactly. */}
|
|
||||||
{showRefPicker ? (
|
|
||||||
<LabeledRow label={t("newWorkspace.fields.base")}>
|
|
||||||
<View>
|
|
||||||
<RefPickerTrigger
|
|
||||||
pickerAnchorRef={pickerAnchorRef}
|
|
||||||
onPress={openPicker}
|
|
||||||
disabled={isPending || !selectedSourceDirectory}
|
|
||||||
badgePressableStyle={badgePressableStyle}
|
|
||||||
selectedItem={selectedItem}
|
|
||||||
triggerLabel={triggerLabel}
|
|
||||||
accessibilityLabel={t("newWorkspace.refPicker.startingRef")}
|
|
||||||
tooltipLabel={t("newWorkspace.refPicker.chooseStart")}
|
|
||||||
iconColor={theme.colors.foregroundMuted}
|
|
||||||
iconSize={theme.iconSize.sm}
|
|
||||||
/>
|
|
||||||
<Combobox
|
|
||||||
options={options}
|
|
||||||
value={selectedOptionId}
|
|
||||||
onSelect={handleSelectOption}
|
|
||||||
searchable
|
|
||||||
searchPlaceholder={t("newWorkspace.refPicker.searchPlaceholder")}
|
|
||||||
title={t("newWorkspace.refPicker.title")}
|
|
||||||
open={pickerOpen}
|
|
||||||
onOpenChange={handlePickerOpenChange}
|
|
||||||
onSearchQueryChange={setPickerSearchQuery}
|
|
||||||
desktopPlacement="bottom-start"
|
|
||||||
anchorRef={pickerAnchorRef}
|
|
||||||
emptyText={pickerEmptyText}
|
|
||||||
renderOption={renderPickerOption}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</LabeledRow>
|
|
||||||
) : (
|
|
||||||
<View style={styles.baseSpacer} />
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
),
|
);
|
||||||
[
|
|
||||||
backingOptions,
|
const isolationControl = canCreateWorktree ? (
|
||||||
backingPickerOpen,
|
<View>
|
||||||
backingTriggerLabel,
|
<IsolationPickerTrigger
|
||||||
badgePressableStyle,
|
pickerAnchorRef={backingPickerAnchorRef}
|
||||||
canCreateWorktree,
|
onPress={openBackingPicker}
|
||||||
effectiveBacking,
|
disabled={isPending}
|
||||||
handleBackingPickerOpenChange,
|
badgePressableStyle={badgePressableStyle}
|
||||||
handlePickerOpenChange,
|
backing={effectiveBacking}
|
||||||
handleProjectPickerOpenChange,
|
label={backingTriggerLabel}
|
||||||
handleSelectBackingOption,
|
iconColor={theme.colors.foregroundMuted}
|
||||||
handleSelectOption,
|
iconSize={theme.iconSize.sm}
|
||||||
handleSelectProjectOption,
|
/>
|
||||||
isPending,
|
<Combobox
|
||||||
openBackingPicker,
|
options={backingOptions}
|
||||||
openPicker,
|
value={effectiveBacking}
|
||||||
openProjectPicker,
|
onSelect={handleSelectBackingOption}
|
||||||
options,
|
title={t("newWorkspace.backing.label")}
|
||||||
pickerEmptyText,
|
open={backingPickerOpen}
|
||||||
pickerOpen,
|
onOpenChange={handleBackingPickerOpenChange}
|
||||||
projectPickerOpen,
|
desktopPlacement="bottom-start"
|
||||||
projectPickerOptions,
|
anchorRef={backingPickerAnchorRef}
|
||||||
projectTriggerLabel,
|
renderOption={renderBackingOption}
|
||||||
projectIconDataByProjectKey,
|
/>
|
||||||
renderBackingOption,
|
</View>
|
||||||
renderPickerOption,
|
) : null;
|
||||||
renderProjectOption,
|
|
||||||
selectedItem,
|
const baseControl = showRefPicker ? (
|
||||||
selectedOptionId,
|
<View>
|
||||||
selectedProject,
|
<RefPickerTrigger
|
||||||
selectedProjectOptionId,
|
pickerAnchorRef={pickerAnchorRef}
|
||||||
selectedSourceDirectory,
|
onPress={openPicker}
|
||||||
setPickerSearchQuery,
|
disabled={isPending || !selectedSourceDirectory}
|
||||||
showRefPicker,
|
badgePressableStyle={badgePressableStyle}
|
||||||
t,
|
selectedItem={selectedItem}
|
||||||
theme.colors.foregroundMuted,
|
triggerLabel={triggerLabel}
|
||||||
theme.iconSize.sm,
|
accessibilityLabel={t("newWorkspace.refPicker.startingRef")}
|
||||||
triggerLabel,
|
tooltipLabel={t("newWorkspace.refPicker.chooseStart")}
|
||||||
],
|
iconColor={theme.colors.foregroundMuted}
|
||||||
);
|
iconSize={theme.iconSize.sm}
|
||||||
|
/>
|
||||||
|
<Combobox
|
||||||
|
options={options}
|
||||||
|
value={selectedOptionId}
|
||||||
|
onSelect={handleSelectOption}
|
||||||
|
searchable
|
||||||
|
searchPlaceholder={t("newWorkspace.refPicker.searchPlaceholder")}
|
||||||
|
title={t("newWorkspace.refPicker.title")}
|
||||||
|
open={pickerOpen}
|
||||||
|
onOpenChange={handlePickerOpenChange}
|
||||||
|
onSearchQueryChange={setPickerSearchQuery}
|
||||||
|
desktopPlacement="bottom-start"
|
||||||
|
anchorRef={pickerAnchorRef}
|
||||||
|
emptyText={pickerEmptyText}
|
||||||
|
renderOption={renderPickerOption}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
if (isCompact) {
|
||||||
|
return (
|
||||||
|
<View testID="new-workspace-ref-picker-row" style={styles.formStack}>
|
||||||
|
<FormRow>{projectControl}</FormRow>
|
||||||
|
{/* The Isolation row keeps its height for non-git projects so switching
|
||||||
|
projects never shifts the form; worktree backing is git-only, so a
|
||||||
|
non-git project renders an invisible spacer matching the trigger
|
||||||
|
height exactly. */}
|
||||||
|
{isolationControl ? (
|
||||||
|
<FormRow>{isolationControl}</FormRow>
|
||||||
|
) : (
|
||||||
|
<View style={styles.baseSpacer} />
|
||||||
|
)}
|
||||||
|
{/* The Base row keeps its height so toggling Isolation never shifts the
|
||||||
|
form; on Local backing it renders an invisible spacer with no label
|
||||||
|
or control, matching the trigger height exactly. */}
|
||||||
|
{baseControl ? <FormRow>{baseControl}</FormRow> : <View style={styles.baseSpacer} />}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View testID="new-workspace-ref-picker-row" style={styles.formStackDesktop}>
|
||||||
|
{projectControl}
|
||||||
|
{isolationControl}
|
||||||
|
{baseControl}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
backingOptions,
|
||||||
|
backingPickerOpen,
|
||||||
|
backingTriggerLabel,
|
||||||
|
badgePressableStyle,
|
||||||
|
canCreateWorktree,
|
||||||
|
effectiveBacking,
|
||||||
|
handleBackingPickerOpenChange,
|
||||||
|
handlePickerOpenChange,
|
||||||
|
handleProjectPickerOpenChange,
|
||||||
|
handleSelectBackingOption,
|
||||||
|
handleSelectOption,
|
||||||
|
handleSelectProjectOption,
|
||||||
|
isCompact,
|
||||||
|
isPending,
|
||||||
|
openBackingPicker,
|
||||||
|
openPicker,
|
||||||
|
openProjectPicker,
|
||||||
|
options,
|
||||||
|
pickerEmptyText,
|
||||||
|
pickerOpen,
|
||||||
|
projectIconDataByProjectKey,
|
||||||
|
projectPickerOpen,
|
||||||
|
projectPickerOptions,
|
||||||
|
projectTriggerLabel,
|
||||||
|
renderBackingOption,
|
||||||
|
renderPickerOption,
|
||||||
|
renderProjectOption,
|
||||||
|
selectedItem,
|
||||||
|
selectedOptionId,
|
||||||
|
selectedProject,
|
||||||
|
selectedProjectOptionId,
|
||||||
|
selectedSourceDirectory,
|
||||||
|
setPickerSearchQuery,
|
||||||
|
showRefPicker,
|
||||||
|
t,
|
||||||
|
theme.colors.foregroundMuted,
|
||||||
|
theme.iconSize.sm,
|
||||||
|
triggerLabel,
|
||||||
|
]);
|
||||||
|
|
||||||
const composerFooter = useMemo(
|
const composerFooter = useMemo(
|
||||||
() => (
|
() => (
|
||||||
@@ -1804,19 +1808,24 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
marginBottom: theme.spacing[8],
|
marginBottom: theme.spacing[8],
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
},
|
},
|
||||||
|
formStackDesktop: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: theme.spacing[8],
|
||||||
|
// The badge adds its own left padding; offset it so the project icon's left
|
||||||
|
// edge lands exactly on the "New workspace" title's left edge.
|
||||||
|
paddingLeft: theme.spacing[4],
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
},
|
||||||
// The row's left inset matches the heading's text x (composerTitleContainer
|
// The row's left inset matches the heading's text x (composerTitleContainer
|
||||||
// paddingLeft) so each label glyph aligns with the "New workspace" glyph. The
|
// paddingLeft) so the control aligns with the "New workspace" glyph. The badge
|
||||||
// label keeps its natural width and the control sits immediately beside it.
|
// adds its own left padding, so the row inset is reduced by that amount.
|
||||||
row: {
|
row: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingLeft: theme.spacing[6],
|
paddingLeft: theme.spacing[4],
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
rowLabel: {
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
},
|
|
||||||
baseSpacer: {
|
baseSpacer: {
|
||||||
height: BADGE_HEIGHT,
|
height: BADGE_HEIGHT,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import type { TFunction } from "i18next";
|
|||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ChevronDown,
|
|
||||||
Monitor,
|
Monitor,
|
||||||
Settings,
|
Settings,
|
||||||
Palette,
|
Palette,
|
||||||
@@ -43,6 +42,8 @@ import {
|
|||||||
FolderGit2,
|
FolderGit2,
|
||||||
SquareTerminal,
|
SquareTerminal,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
|
import { DropdownTrigger } from "@/components/ui/dropdown-trigger";
|
||||||
|
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||||
import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row";
|
import { SidebarHeaderRow } from "@/components/sidebar/sidebar-header-row";
|
||||||
import { SidebarSeparator } from "@/components/sidebar/sidebar-separator";
|
import { SidebarSeparator } from "@/components/sidebar/sidebar-separator";
|
||||||
import { ScreenTitle } from "@/components/headers/screen-title";
|
import { ScreenTitle } from "@/components/headers/screen-title";
|
||||||
@@ -78,12 +79,7 @@ import { KeyboardShortcutsSection } from "@/screens/settings/keyboard-shortcuts-
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { CommunityLinks } from "@/components/community-links";
|
import { CommunityLinks } from "@/components/community-links";
|
||||||
import { SegmentedControl } from "@/components/ui/segmented-control";
|
import { SegmentedControl } from "@/components/ui/segmented-control";
|
||||||
import {
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
||||||
import { DesktopPermissionsSection } from "@/desktop/components/desktop-permissions-section";
|
import { DesktopPermissionsSection } from "@/desktop/components/desktop-permissions-section";
|
||||||
import { IntegrationsSection } from "@/desktop/components/integrations-section";
|
import { IntegrationsSection } from "@/desktop/components/integrations-section";
|
||||||
@@ -312,10 +308,8 @@ function GeneralSection({
|
|||||||
handleLanguageChange,
|
handleLanguageChange,
|
||||||
handleTerminalScrollbackLinesChange,
|
handleTerminalScrollbackLinesChange,
|
||||||
}: GeneralSectionProps) {
|
}: GeneralSectionProps) {
|
||||||
const { theme } = useUnistyles();
|
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const activeLocale = getActiveLocale(i18n.language);
|
const activeLocale = getActiveLocale(i18n.language);
|
||||||
const iconColor = theme.colors.foregroundMuted;
|
|
||||||
const sendBehaviorOptions = useMemo(() => getSendBehaviorOptions(t), [t]);
|
const sendBehaviorOptions = useMemo(() => getSendBehaviorOptions(t), [t]);
|
||||||
const sendBehaviorDescriptionKey =
|
const sendBehaviorDescriptionKey =
|
||||||
settings.sendBehavior === "interrupt"
|
settings.sendBehavior === "interrupt"
|
||||||
@@ -377,14 +371,13 @@ function GeneralSection({
|
|||||||
<Text style={settingsStyles.rowHint}>{t("settings.general.language.description")}</Text>
|
<Text style={settingsStyles.rowHint}>{t("settings.general.language.description")}</Text>
|
||||||
</View>
|
</View>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownTrigger
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={selectedLanguageLabel}
|
accessibilityLabel={selectedLanguageLabel}
|
||||||
style={themeTriggerStyle}
|
style={themeTriggerStyle}
|
||||||
>
|
>
|
||||||
<Text style={styles.themeTriggerText}>{selectedLanguageLabel}</Text>
|
<Text style={styles.themeTriggerText}>{selectedLanguageLabel}</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={iconColor} />
|
</DropdownTrigger>
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent side="bottom" align="end" width={220}>
|
<DropdownMenuContent side="bottom" align="end" width={220}>
|
||||||
{LANGUAGE_OPTIONS.map((option) => (
|
{LANGUAGE_OPTIONS.map((option) => (
|
||||||
<LanguageMenuItem
|
<LanguageMenuItem
|
||||||
@@ -407,12 +400,11 @@ function GeneralSection({
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger style={themeTriggerStyle}>
|
<DropdownTrigger style={themeTriggerStyle}>
|
||||||
<Text style={styles.themeTriggerText}>
|
<Text style={styles.themeTriggerText}>
|
||||||
{getServiceUrlBehaviorLabel(t, settings.serviceUrlBehavior)}
|
{getServiceUrlBehaviorLabel(t, settings.serviceUrlBehavior)}
|
||||||
</Text>
|
</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={iconColor} />
|
</DropdownTrigger>
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent side="bottom" align="end" width={200}>
|
<DropdownMenuContent side="bottom" align="end" width={200}>
|
||||||
{SERVICE_URL_BEHAVIOR_VALUES.map((value) => (
|
{SERVICE_URL_BEHAVIOR_VALUES.map((value) => (
|
||||||
<ServiceUrlBehaviorMenuItem
|
<ServiceUrlBehaviorMenuItem
|
||||||
@@ -1067,7 +1059,7 @@ function HostPicker({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Pressable
|
<ComboboxTrigger
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
style={triggerStyle}
|
style={triggerStyle}
|
||||||
onPress={handleOpen}
|
onPress={handleOpen}
|
||||||
@@ -1079,8 +1071,7 @@ function HostPicker({
|
|||||||
<Text style={sidebarStyles.pickerTriggerLabel} numberOfLines={1}>
|
<Text style={sidebarStyles.pickerTriggerLabel} numberOfLines={1}>
|
||||||
{activeHost?.label ?? t("settings.groups.host")}
|
{activeHost?.label ?? t("settings.groups.host")}
|
||||||
</Text>
|
</Text>
|
||||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
</ComboboxTrigger>
|
||||||
</Pressable>
|
|
||||||
<Combobox
|
<Combobox
|
||||||
options={options}
|
options={options}
|
||||||
value={activeServerId ?? ""}
|
value={activeServerId ?? ""}
|
||||||
|
|||||||
Reference in New Issue
Block a user