Skip to content

Commit b34d7d8

Browse files
Bogdan TsechoevNikolayS
Bogdan Tsechoev
authored andcommitted
Bot UI: Enhance UX on mobile
1 parent 12c0ed5 commit b34d7d8

File tree

6 files changed

+87
-17
lines changed

6 files changed

+87
-17
lines changed

ui/packages/platform/src/components/ContentLayout/Footer/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export const Footer = () => {
6868
const classes = useStyles()
6969

7070
return (
71-
<div className={classes.footer}>
71+
<footer className={classes.footer}>
7272
<div className={classes.footerCopyrightItem}>
7373
{new Date().getFullYear()} © Postgres.AI
7474
</div>
@@ -103,6 +103,6 @@ export const Footer = () => {
103103
</GatewayLink>
104104
</div>
105105
</div>
106-
</div>
106+
</footer>
107107
)
108108
}

ui/packages/platform/src/pages/Bot/ChatsList/ChatsList.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import React from "react";
99
import { Link } from "react-router-dom";
1010
import { useParams } from "react-router";
1111
import cn from "classnames";
12-
import { makeStyles, Theme } from "@material-ui/core";
12+
import { makeStyles, Theme, useMediaQuery } from "@material-ui/core";
1313
import Drawer from '@material-ui/core/Drawer';
1414
import List from "@material-ui/core/List";
1515
import Divider from "@material-ui/core/Divider";
@@ -18,6 +18,7 @@ import Box from "@mui/material/Box";
1818
import { Spinner } from "@postgres.ai/shared/components/Spinner";
1919
import { HeaderButtons, HeaderButtonsProps } from "../HeaderButtons/HeaderButtons";
2020
import { BotMessage } from "../../../types/api/entities/bot";
21+
import { theme } from "@postgres.ai/shared/styles/theme";
2122

2223

2324
const useStyles = makeStyles<Theme, ChatsListProps>((theme) => ({
@@ -111,7 +112,7 @@ export const ChatsList = (props: ChatsListProps) => {
111112
} = props;
112113
const classes = useStyles(props);
113114
const params = useParams<{ org?: string, threadId?: string }>();
114-
115+
const matches = useMediaQuery(theme.breakpoints.down('sm'));
115116
const linkBuilder = (msgId: string) => {
116117
if (params.org) {
117118
return `/${params.org}/bot/${msgId}`
@@ -126,6 +127,12 @@ export const ChatsList = (props: ChatsListProps) => {
126127
}
127128
}
128129

130+
const handleCloseOnClickOutside = () => {
131+
if (matches) {
132+
onClose()
133+
}
134+
}
135+
129136
const loader = (
130137
<Box className={classes.loader}>
131138
<Spinner/>
@@ -172,11 +179,12 @@ export const ChatsList = (props: ChatsListProps) => {
172179

173180
return (
174181
<Drawer
175-
variant={'persistent'}
182+
variant={matches ? 'temporary' : 'persistent'}
176183
anchor="right"
177184
BackdropProps={{ invisible: true }}
178185
elevation={1}
179186
open={isOpen}
187+
onClose={handleCloseOnClickOutside}
180188
classes={{
181189
paper: classes.drawerPaper
182190
}}

ui/packages/platform/src/pages/Bot/Command/Command.tsx

+20-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { useBuffer } from './useBuffer'
1414
import { useCaret } from './useCaret'
1515
import { theme } from "@postgres.ai/shared/styles/theme";
16+
import { isMobileDevice } from "../../../utils/utils";
1617

1718

1819
type Props = {
@@ -76,7 +77,7 @@ export const Command = React.memo((props: Props) => {
7677
const { sendDisabled, onSend, threadId } = props
7778

7879
const classes = useStyles()
79-
80+
const isMobile = isMobileDevice();
8081
// Handle value.
8182
const [value, setValue] = useState('')
8283

@@ -104,11 +105,21 @@ export const Command = React.memo((props: Props) => {
104105
}
105106

106107
const handleBlur = () => {
107-
if (window.innerWidth < theme.breakpoints.values.sm)
108-
window.scrollTo({
109-
top: 0,
110-
behavior: 'smooth'
111-
})
108+
if ((window.innerWidth < theme.breakpoints.values.sm) && isMobile) {
109+
window.scrollTo({
110+
top: 0,
111+
behavior: 'smooth'
112+
})
113+
const footer: HTMLElement | null = document.querySelector("footer")
114+
if (footer) footer.style.display = 'flex';
115+
}
116+
}
117+
118+
const handleFocus = () => {
119+
if ((window.innerWidth < theme.breakpoints.values.sm) && isMobile) {
120+
const footer: HTMLElement | null = document.querySelector("footer")
121+
if (footer) footer.style.display = 'none';
122+
}
112123
}
113124

114125
const handleKeyDown = (e: React.KeyboardEvent) => {
@@ -163,19 +174,20 @@ export const Command = React.memo((props: Props) => {
163174
return (
164175
<div className={classes.root}>
165176
<TextField
166-
autoFocus={true}
177+
autoFocus={window.innerWidth > theme.breakpoints.values.sm}
167178
multiline
168179
className={classes.field}
169180
onKeyDown={handleKeyDown}
181+
onChange={handleChange}
170182
onBlur={handleBlur}
183+
onFocus={handleFocus}
171184
InputProps={{
172185
inputRef,
173186
classes: {
174187
input: classes.fieldInput,
175188
},
176189
}}
177190
value={value}
178-
onChange={handleChange}
179191
placeholder="Message..."
180192
/>
181193
<IconButton

ui/packages/platform/src/pages/Bot/Command/utils.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
export const checkIsSendCmd = (e: KeyboardEvent) =>
2-
e.code === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey
1+
import { isMobileDevice } from "../../../utils/utils";
32

4-
export const checkIsNewLineCmd = (e: KeyboardEvent) =>
5-
e.code === 'Enter' && (e.shiftKey || e.ctrlKey || e.metaKey)
3+
export const checkIsSendCmd = (e: KeyboardEvent): boolean => {
4+
if (isMobileDevice()) {
5+
return false; // On mobile devices, Enter should not send.
6+
}
7+
return e.code === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey;
8+
};
9+
10+
export const checkIsNewLineCmd = (e: KeyboardEvent): boolean => {
11+
if (isMobileDevice()) {
12+
return e.code === 'Enter'; // On mobile devices, Enter should create a new line.
13+
}
14+
return e.code === 'Enter' && (e.shiftKey || e.ctrlKey || e.metaKey);
15+
};
616

717
export const addNewLine = (
818
value: string,

ui/packages/platform/src/utils/utils.ts

+38
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,41 @@ export const validateDLEName = (name: string) => {
3131
!name.match(/^([a-z](?:[-a-z0-9]{0,61}[a-z0-9])?|[1-9][0-9]{0,19})$/)
3232
)
3333
}
34+
35+
export const isMobileDevice = (): boolean => {
36+
let hasTouchScreen = false;
37+
38+
// Check for modern touch screen devices using maxTouchPoints
39+
if ("maxTouchPoints" in navigator) {
40+
hasTouchScreen = navigator.maxTouchPoints > 0;
41+
}
42+
// Check for older versions of IE with msMaxTouchPoints
43+
else if ("msMaxTouchPoints" in navigator) {
44+
hasTouchScreen = (navigator as unknown as { msMaxTouchPoints: number }).msMaxTouchPoints > 0;
45+
}
46+
// Use matchMedia to check for coarse pointer devices
47+
else {
48+
const mQ = window.matchMedia("(pointer:coarse)");
49+
if (mQ && mQ.media === "(pointer:coarse)") {
50+
hasTouchScreen = mQ.matches;
51+
}
52+
// Check for the presence of the orientation property as a fallback (deprecated in modern browsers)
53+
else if ('orientation' in window) {
54+
hasTouchScreen = true;
55+
}
56+
// Last resort: fallback with user agent sniffing
57+
else {
58+
const UA = navigator.userAgent;
59+
hasTouchScreen = (
60+
/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
61+
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
62+
);
63+
}
64+
}
65+
66+
// Check for mobile screen width, 1366 because of iPad Pro in Landscape mode
67+
// If this is not necessary, may reduce value to 1024 or 768
68+
const isMobileScreen = window.innerWidth <= 1366;
69+
70+
return hasTouchScreen && isMobileScreen;
71+
}

ui/packages/shared/components/TextField/index.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export type TextFieldProps = {
3434
error?: boolean
3535
placeholder?: string
3636
onBlur?: TextFieldPropsBase['onBlur']
37+
onFocus?: TextFieldPropsBase['onFocus']
3738
}
3839

3940
const useStyles = makeStyles(
@@ -98,6 +99,7 @@ export const TextField = (props: TextFieldProps) => {
9899
error={props.error}
99100
placeholder={props.placeholder}
100101
onBlur={props.onBlur}
102+
onFocus={props.onFocus}
101103
/>
102104
)
103105
}

0 commit comments

Comments
 (0)