Show attachment actions in a mobile sheet
This commit is contained in:
@@ -2,6 +2,8 @@ import {
|
|||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
Pressable,
|
||||||
|
Platform,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
useWindowDimensions,
|
useWindowDimensions,
|
||||||
NativeSyntheticEvent,
|
NativeSyntheticEvent,
|
||||||
@@ -46,6 +48,8 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} 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 { useWebElementScrollbar } from "@/components/use-web-scrollbar";
|
||||||
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
||||||
import { useIosHardwareKeyboardSubmit } from "@/hooks/use-ios-hardware-keyboard-submit";
|
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 DEFAULT_MAX_INPUT_HEIGHT = 160;
|
||||||
const MAX_INPUT_VIEWPORT_RATIO = 0.5;
|
const MAX_INPUT_VIEWPORT_RATIO = 0.5;
|
||||||
const MIN_INPUT_HEIGHT = isWeb ? MIN_INPUT_HEIGHT_DESKTOP : MIN_INPUT_HEIGHT_MOBILE;
|
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<
|
type WebTextInputKeyPressEvent = NativeSyntheticEvent<
|
||||||
TextInputKeyPressEventData & {
|
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 (
|
||||||
|
<Pressable
|
||||||
|
testID={`message-input-attachment-menu-item-${item.id}`}
|
||||||
|
accessibilityRole="button"
|
||||||
|
disabled={item.disabled}
|
||||||
|
onPress={handlePress}
|
||||||
|
style={pressableStyle}
|
||||||
|
>
|
||||||
|
{item.icon ? <View style={styles.attachmentSheetItemIcon}>{item.icon}</View> : null}
|
||||||
|
<Text style={styles.attachmentSheetItemText}>{item.label}</Text>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttachmentSheetList({
|
||||||
|
items,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
items: AttachmentMenuItem[];
|
||||||
|
onSelect: (item: AttachmentMenuItem) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<View style={styles.attachmentSheetList}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<AttachmentSheetItem key={item.id} item={item} onSelect={onSelect} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function AttachmentDropdown({
|
function AttachmentDropdown({
|
||||||
isConnected,
|
isConnected,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -211,12 +266,73 @@ function AttachmentDropdown({
|
|||||||
renderAttachButtonIcon: (input: { hovered?: boolean }) => React.ReactElement;
|
renderAttachButtonIcon: (input: { hovered?: boolean }) => React.ReactElement;
|
||||||
attachmentMenuItems: AttachmentMenuItem[];
|
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 (
|
||||||
|
<>
|
||||||
|
<Pressable
|
||||||
|
disabled={isButtonDisabled}
|
||||||
|
accessibilityLabel="Add attachment"
|
||||||
|
accessibilityRole="button"
|
||||||
|
testID="message-input-attach-button"
|
||||||
|
onPress={handleOpenSheet}
|
||||||
|
style={mobileAttachButtonStyle}
|
||||||
|
>
|
||||||
|
{renderMobileAttachButtonIcon}
|
||||||
|
</Pressable>
|
||||||
|
<AdaptiveModalSheet
|
||||||
|
header={ATTACHMENT_SHEET_HEADER}
|
||||||
|
visible={isSheetOpen}
|
||||||
|
onClose={handleCloseSheet}
|
||||||
|
snapPoints={ATTACHMENT_SHEET_SNAP_POINTS}
|
||||||
|
testID="message-input-attachment-menu"
|
||||||
|
>
|
||||||
|
<AttachmentSheetList items={attachmentMenuItems} onSelect={handleSheetItemSelect} />
|
||||||
|
</AdaptiveModalSheet>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
disabled={!isConnected || disabled}
|
disabled={isButtonDisabled}
|
||||||
accessibilityLabel="Add attachment"
|
accessibilityLabel="Add attachment"
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
testID="message-input-attach-button"
|
testID="message-input-attach-button"
|
||||||
@@ -1950,6 +2066,31 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
|||||||
buttonDisabled: {
|
buttonDisabled: {
|
||||||
opacity: 0.5,
|
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: {
|
overlayContainer: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
Reference in New Issue
Block a user