diff --git a/packages/app/src/composer/input/input.tsx b/packages/app/src/composer/input/input.tsx
index a565d6085..235520c40 100644
--- a/packages/app/src/composer/input/input.tsx
+++ b/packages/app/src/composer/input/input.tsx
@@ -2,6 +2,8 @@ import {
View,
Text,
TextInput,
+ Pressable,
+ Platform,
ActivityIndicator,
useWindowDimensions,
NativeSyntheticEvent,
@@ -46,6 +48,8 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
+import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet";
+import { useDismissKeyboardOnOpen } from "@/components/ui/keyboard-dismiss";
import { useWebElementScrollbar } from "@/components/use-web-scrollbar";
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
import { useIosHardwareKeyboardSubmit } from "@/hooks/use-ios-hardware-keyboard-submit";
@@ -138,6 +142,8 @@ const MIN_INPUT_HEIGHT_DESKTOP = 46;
const DEFAULT_MAX_INPUT_HEIGHT = 160;
const MAX_INPUT_VIEWPORT_RATIO = 0.5;
const MIN_INPUT_HEIGHT = isWeb ? MIN_INPUT_HEIGHT_DESKTOP : MIN_INPUT_HEIGHT_MOBILE;
+const ATTACHMENT_SHEET_HEADER: SheetHeader = { title: "Add attachment" };
+const ATTACHMENT_SHEET_SNAP_POINTS = ["28%", "45%"];
type WebTextInputKeyPressEvent = NativeSyntheticEvent<
TextInputKeyPressEventData & {
@@ -198,6 +204,55 @@ function AttachmentMenuList({ items }: { items: AttachmentMenuItem[] }) {
);
}
+function AttachmentSheetItem({
+ item,
+ onSelect,
+}: {
+ item: AttachmentMenuItem;
+ onSelect: (item: AttachmentMenuItem) => void;
+}) {
+ const handlePress = useCallback(() => {
+ onSelect(item);
+ }, [item, onSelect]);
+ const pressableStyle = useCallback(
+ ({ pressed }: { pressed: boolean }) => [
+ styles.attachmentSheetItem,
+ pressed && styles.attachmentSheetItemPressed,
+ item.disabled && styles.buttonDisabled,
+ ],
+ [item.disabled],
+ );
+
+ return (
+
+ {item.icon ? {item.icon} : null}
+ {item.label}
+
+ );
+}
+
+function AttachmentSheetList({
+ items,
+ onSelect,
+}: {
+ items: AttachmentMenuItem[];
+ onSelect: (item: AttachmentMenuItem) => void;
+}) {
+ return (
+
+ {items.map((item) => (
+
+ ))}
+
+ );
+}
+
function AttachmentDropdown({
isConnected,
disabled,
@@ -211,12 +266,73 @@ function AttachmentDropdown({
renderAttachButtonIcon: (input: { hovered?: boolean }) => React.ReactElement;
attachmentMenuItems: AttachmentMenuItem[];
}) {
+ const isCompact = useIsCompactFormFactor();
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
+ useDismissKeyboardOnOpen(isSheetOpen, isCompact);
+
+ const isButtonDisabled = !isConnected || disabled;
+ const handleOpenSheet = useCallback(() => {
+ if (isButtonDisabled) return;
+ setIsSheetOpen(true);
+ }, [isButtonDisabled]);
+ const handleCloseSheet = useCallback(() => {
+ setIsSheetOpen(false);
+ }, []);
+ const handleSheetItemSelect = useCallback((item: AttachmentMenuItem) => {
+ if (item.disabled) return;
+ setIsSheetOpen(false);
+ if (Platform.OS === "ios") {
+ setTimeout(item.onSelect, 250);
+ return;
+ }
+ item.onSelect();
+ }, []);
+ const mobileAttachButtonStyle = useCallback(
+ (state: { pressed: boolean; hovered?: boolean }) => {
+ if (typeof attachButtonStyle === "function") {
+ return attachButtonStyle({ ...state, hovered: Boolean(state.hovered), open: isSheetOpen });
+ }
+ return attachButtonStyle;
+ },
+ [attachButtonStyle, isSheetOpen],
+ );
+ const renderMobileAttachButtonIcon = useCallback(
+ ({ hovered }: { hovered?: boolean }) => renderAttachButtonIcon({ hovered }),
+ [renderAttachButtonIcon],
+ );
+
+ if (isCompact) {
+ return (
+ <>
+
+ {renderMobileAttachButtonIcon}
+
+
+
+
+ >
+ );
+ }
+
return (
({
buttonDisabled: {
opacity: 0.5,
},
+ attachmentSheetList: {
+ gap: theme.spacing[1],
+ },
+ attachmentSheetItem: {
+ minHeight: 44,
+ flexDirection: "row",
+ alignItems: "center",
+ gap: theme.spacing[3],
+ paddingHorizontal: theme.spacing[3],
+ paddingVertical: theme.spacing[2],
+ borderRadius: theme.borderRadius.xl,
+ },
+ attachmentSheetItemPressed: {
+ backgroundColor: theme.colors.surface2,
+ },
+ attachmentSheetItemIcon: {
+ width: 20,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ attachmentSheetItemText: {
+ color: theme.colors.foreground,
+ fontSize: theme.fontSize.base,
+ fontWeight: theme.fontWeight.normal,
+ },
overlayContainer: {
position: "absolute",
display: "flex",