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 { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { isNative, isWeb as platformIsWeb } from "@/constants/platform";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Search,
|
||||
Settings,
|
||||
Star,
|
||||
} from "lucide-react-native";
|
||||
import { AlertTriangle, ChevronRight, Search, Settings, Star } from "lucide-react-native";
|
||||
import { ComboboxTrigger } from "@/components/ui/combobox-trigger";
|
||||
import type { AgentProvider } from "@getpaseo/protocol/agent-types";
|
||||
import type { SheetHeader } from "@/components/adaptive-modal-sheet";
|
||||
import { useProviderSettingsStore } from "@/stores/provider-settings-store";
|
||||
@@ -784,35 +778,43 @@ export function CombinedModelSelector({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pressable
|
||||
ref={anchorRef}
|
||||
collapsable={false}
|
||||
disabled={disabled}
|
||||
onPress={handleTriggerPress}
|
||||
style={triggerStyle}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t("modelSelector.selectedModel", { model: selectedModelLabel })}
|
||||
testID="combined-model-selector"
|
||||
>
|
||||
{renderTrigger ? (
|
||||
renderTrigger({
|
||||
{renderTrigger ? (
|
||||
<Pressable
|
||||
ref={anchorRef}
|
||||
collapsable={false}
|
||||
disabled={disabled}
|
||||
onPress={handleTriggerPress}
|
||||
style={triggerStyle}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t("modelSelector.selectedModel", { model: selectedModelLabel })}
|
||||
testID="combined-model-selector"
|
||||
>
|
||||
{renderTrigger({
|
||||
selectedModelLabel: triggerLabel,
|
||||
onPress: handleTriggerPress,
|
||||
disabled,
|
||||
isOpen,
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
{ProviderIcon ? (
|
||||
<ProviderIcon size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||
) : null}
|
||||
<Text style={styles.triggerText} numberOfLines={1} ellipsizeMode="tail">
|
||||
{triggerLabel}
|
||||
</Text>
|
||||
<ChevronDown size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
})}
|
||||
</Pressable>
|
||||
) : (
|
||||
<ComboboxTrigger
|
||||
ref={anchorRef}
|
||||
collapsable={false}
|
||||
disabled={disabled}
|
||||
onPress={handleTriggerPress}
|
||||
style={triggerStyle}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t("modelSelector.selectedModel", { model: selectedModelLabel })}
|
||||
testID="combined-model-selector"
|
||||
>
|
||||
{ProviderIcon ? (
|
||||
<ProviderIcon size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||
) : null}
|
||||
<Text style={styles.triggerText} numberOfLines={1} ellipsizeMode="tail">
|
||||
{triggerLabel}
|
||||
</Text>
|
||||
</ComboboxTrigger>
|
||||
)}
|
||||
<Combobox
|
||||
options={EMPTY_COMBOBOX_OPTIONS}
|
||||
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>);
|
||||
|
||||
interface DropdownMenuTriggerProps extends Omit<PressableProps, "style" | "children"> {
|
||||
export interface DropdownMenuTriggerProps extends Omit<PressableProps, "style" | "children"> {
|
||||
style?: TriggerStyleProp;
|
||||
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 }],
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user