📱 feat: Optimize mobile display
This commit is contained in:
parent
3ce64a3cc3
commit
59a7ab046b
7 changed files with 62 additions and 23 deletions
|
|
@ -37,7 +37,7 @@ export default function ChatSelect({ disabled }: { disabled: boolean }) {
|
|||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-[250px] justify-between"
|
||||
className="w-full md:w-[250px] justify-between"
|
||||
>
|
||||
{selectedChat ? (
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export function FileCard({ file }: FileCardProps) {
|
|||
<div className="flex-1">
|
||||
<h3 className="mb-1 font-medium">{file.name}</h3>
|
||||
<div className="mb-2 text-sm text-muted-foreground">
|
||||
{prettyBytes(file.size)} • {file.type}
|
||||
{prettyBytes(file.size)} • {file.type} • {file.downloadStatus}
|
||||
</div>
|
||||
<div className="mb-2 w-full overflow-hidden">
|
||||
<FileProgress file={file} />
|
||||
|
|
|
|||
|
|
@ -39,14 +39,14 @@ export function FileFilters({
|
|||
}: FileFiltersProps) {
|
||||
return (
|
||||
<div className="mb-6 flex flex-col justify-between md:flex-row">
|
||||
<div className="flex flex-col gap-4 md:flex-row">
|
||||
<div className="grid grid-cols-2 gap-4 md:flex-row md:flex">
|
||||
<Input
|
||||
placeholder="Search files..."
|
||||
value={filters.search}
|
||||
onChange={(e) =>
|
||||
onFiltersChange({ ...filters, search: e.target.value })
|
||||
}
|
||||
className="md:w-[300px]"
|
||||
className="col-span-2 md:w-[300px]"
|
||||
/>
|
||||
|
||||
<FileTypeFilter
|
||||
|
|
@ -65,7 +65,7 @@ export function FileFilters({
|
|||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectTrigger className="w-full md:w-[150px]">
|
||||
<SelectValue placeholder="Download status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -78,7 +78,7 @@ export function FileFilters({
|
|||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<div className="hidden gap-4 md:flex">
|
||||
<TableColumnFilter
|
||||
columns={columns}
|
||||
onColumnConfigChange={onColumnConfigChange}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import prettyBytes from "pretty-bytes";
|
|||
import FileProgress from "@/components/file-progress";
|
||||
import FileNotFount from "@/components/file-not-found";
|
||||
import FileExtra from "@/components/file-extra";
|
||||
import useIsMobile from "@/hooks/use-is-mobile";
|
||||
|
||||
interface FileListProps {
|
||||
accountId: string;
|
||||
|
|
@ -100,7 +101,7 @@ const PhotoColumnImage = memo(function PhotoColumnImage({
|
|||
});
|
||||
|
||||
export function FileList({ accountId, chatId }: FileListProps) {
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
|
||||
const observerTarget = useRef(null);
|
||||
const [columns, setColumns] = useState<Column[]>(COLUMNS);
|
||||
|
|
@ -110,16 +111,6 @@ export function FileList({ accountId, chatId }: FileListProps) {
|
|||
const { filters, handleFilterChange, isLoading, files, handleLoadMore } =
|
||||
useFiles(accountId, chatId);
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
setIsMobile(window.innerWidth < 768);
|
||||
};
|
||||
|
||||
checkMobile();
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
|
|
@ -162,6 +153,16 @@ export function FileList({ accountId, chatId }: FileListProps) {
|
|||
if (isMobile) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<FileFilters
|
||||
telegramId={accountId}
|
||||
chatId={chatId}
|
||||
filters={filters}
|
||||
onFiltersChange={handleFilterChange}
|
||||
columns={columns}
|
||||
onColumnConfigChange={setColumns}
|
||||
rowHeight={rowHeight}
|
||||
setRowHeight={setRowHeight}
|
||||
/>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{files.map((file, index) => (
|
||||
<FileCard
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export default function FileTypeFilter({
|
|||
value={type}
|
||||
onValueChange={(value) => onTypeChange(value as FileType)}
|
||||
>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectTrigger className="w-full md:w-[150px]">
|
||||
<SelectValue placeholder="File type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
|
|||
|
|
@ -30,23 +30,28 @@ import ChatSelect from "@/components/chat-select";
|
|||
import Link from "next/link";
|
||||
import TelegramIcon from "@/components/telegram-icon";
|
||||
import AutoDownloadDialog from "@/components/auto-download-dialog";
|
||||
import useIsMobile from "@/hooks/use-is-mobile";
|
||||
import { useState } from "react";
|
||||
import {Button} from "@/components/ui/button";
|
||||
|
||||
export function Header() {
|
||||
const { isLoading, getAccounts, accountId, account, handleAccountChange } =
|
||||
useTelegramAccount();
|
||||
const { connectionStatus, accountDownloadSpeed } = useWebsocket();
|
||||
const accounts = getAccounts("active");
|
||||
const isMobile = useIsMobile();
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
return (
|
||||
<Card className="mb-6">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex flex-col items-start justify-between gap-4 md:flex-row md:items-center">
|
||||
<div className="flex flex-1 flex-col gap-4 md:flex-row md:items-center">
|
||||
<div className="relative flex flex-col items-start justify-between gap-4 md:flex-row md:items-center">
|
||||
<div className="w-full flex flex-1 flex-col gap-4 md:flex-row md:items-center">
|
||||
<Link href={"/"} className="hidden md:inline-flex">
|
||||
<TelegramIcon className="h-6 w-6" />
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-full md:w-auto">
|
||||
<Select value={accountId} onValueChange={handleAccountChange}>
|
||||
<SelectTrigger className="w-full md:w-[200px]">
|
||||
<SelectValue placeholder="Select account ...">
|
||||
|
|
@ -99,9 +104,14 @@ export function Header() {
|
|||
</Select>
|
||||
</div>
|
||||
|
||||
<ChatSelect disabled={!accountId} />
|
||||
{(!isMobile || showMore) && (
|
||||
<>
|
||||
<ChatSelect disabled={!accountId} />
|
||||
|
||||
<AutoDownloadDialog />
|
||||
</>
|
||||
)}
|
||||
|
||||
<AutoDownloadDialog />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
|
|
@ -147,6 +157,15 @@ export function Header() {
|
|||
|
||||
<SettingsDialog />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
onClick={() => setShowMore(!showMore)}
|
||||
className="absolute bottom-0 right-0 md:hidden"
|
||||
>
|
||||
<Ellipsis className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
19
web/src/hooks/use-is-mobile.ts
Normal file
19
web/src/hooks/use-is-mobile.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useLayoutEffect, useState } from "react";
|
||||
|
||||
const useIsMobile = (): boolean => {
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const checkMobile = () => {
|
||||
setIsMobile(window.innerWidth < 768);
|
||||
};
|
||||
|
||||
checkMobile();
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
}, []);
|
||||
|
||||
return isMobile;
|
||||
};
|
||||
|
||||
export default useIsMobile;
|
||||
Loading…
Reference in a new issue