Skip to content

Commit 71be91f

Browse files
committed
Grid mode single expanded item view
1 parent 942b84e commit 71be91f

File tree

11 files changed

+152
-93
lines changed

11 files changed

+152
-93
lines changed

src/components/downloaded/audio-player.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useEffect, type FC } from 'react';
55
import { UvxYtdlpIcon } from '@/components/branding/uvxytdlp-icon';
66
import { useAudioPlayerContext } from '@/contexts/audio-player-context-provider';
77
import { PauseIcon, PlayIcon } from 'lucide-react';
8-
import { thinIconStyle } from '@/lib/icon-style';
8+
import { thinIconStyle } from '@/lib/style';
99

1010
interface AudioPlayerProps {
1111
fileName: string;
Lines changed: 134 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,82 @@
1-
import { UvxYtdlpIcon } from "@/components/branding/uvxytdlp-icon"
2-
import { gridButtonClasses, gridClasses, gridNameClasses, listButtonClasses, listClasses, listNameClasses, roundButtonClasses, thinIconStyle } from "@/lib/icon-style"
3-
import LongPressButton from "@/components/ocodo-ui/long-press-button"
4-
import { useApiBase } from "@/contexts/api-base-context"
5-
import { useDownloaded, type DownloadedFileType } from "@/contexts/downloaded-context"
6-
import { DownloadIcon, PlayIcon, Trash2Icon } from "lucide-react"
1+
import { UvxYtdlpIcon } from "@/components/branding/uvxytdlp-icon";
2+
import {
3+
gridButtonClasses,
4+
gridClasses,
5+
gridNameClasses,
6+
listButtonClasses,
7+
listClasses,
8+
listNameClasses,
9+
roundButtonClasses,
10+
thinIconStyle,
11+
} from "@/lib/style";
12+
import LongPressButton from "@/components/ocodo-ui/long-press-button";
13+
import { useApiBase } from "@/contexts/api-base-context";
14+
import {
15+
useDownloaded,
16+
type DownloadedFileType,
17+
} from "@/contexts/downloaded-context";
18+
import {
19+
DownloadIcon,
20+
EllipsisVerticalIcon,
21+
PlayIcon,
22+
Trash2Icon,
23+
} from "lucide-react";
724

8-
import type React from "react"
9-
import { Img } from "react-image"
25+
import { Img } from "react-image";
26+
import { cn } from "@/lib/utils";
27+
import { type FC } from "react";
1028

1129
interface DowloadedFileProps {
12-
file: DownloadedFileType
13-
handlePlay: (name: string) => void
14-
handleDelete: (name: string) => void
15-
handleDownload: (name: string) => void
16-
selectedFile: string | undefined
17-
isDeleting: string | undefined
30+
file: DownloadedFileType;
31+
handlePlay: (name: string) => void;
32+
handleDelete: (name: string) => void;
33+
handleDownload: (name: string) => void;
34+
selectedFile?: string;
35+
isDeleting?: string;
36+
isExpanded: boolean;
37+
onToggleExpand: () => void;
1838
}
1939

20-
export const DowloadedFile: React.FC<DowloadedFileProps> = (props) => {
21-
const { file, handlePlay, handleDownload, handleDelete, isDeleting } = props
22-
const { apiBase } = useApiBase()
23-
const { viewType } = useDownloaded()
40+
export const DowloadedFile: FC<DowloadedFileProps> = (props) => {
41+
const {
42+
file,
43+
handlePlay,
44+
handleDownload,
45+
handleDelete,
46+
isDeleting,
47+
isExpanded,
48+
onToggleExpand,
49+
} = props;
50+
51+
const { apiBase } = useApiBase();
52+
const { viewType } = useDownloaded();
53+
const isGrid = viewType === "grid";
54+
const isList = viewType === "list";
2455

2556
const formatDuration = (duration: string | null) => {
26-
if (duration == null) {
27-
return ''
28-
}
29-
if (duration?.includes(':')) {
30-
return duration
31-
} else {
32-
return `${duration}s`
33-
}
34-
}
57+
if (!duration) return "";
58+
return duration.includes(":") ? duration : `${duration}s`;
59+
};
3560

3661
const ContentImage = () => (
3762
<div
3863
className="flex flex-col justify-center items-center relative gap-1 bg-black rounded-t-xl group"
39-
onClick={() => handlePlay(file.name)}>
40-
<div className={`absolute cursor-pointer opacity-10
41-
group-hover:opacity-100
42-
transition-opacity duration-500
43-
rounded-full bg-background/30
44-
w-20 h-20 mb-[22px]
45-
flex flex-row items-center justify-center`}>
64+
onClick={() => handlePlay(file.name)}
65+
>
66+
<div
67+
className="
68+
absolute cursor-pointer opacity-10 group-hover:opacity-100
69+
transition-opacity duration-500 rounded-full bg-background/30
70+
w-20 h-20 mb-[22px] flex items-center justify-center
71+
"
72+
>
4673
<PlayIcon
47-
style={{ stroke: '#fff', ...thinIconStyle }}
48-
className="w-12 h-12 ml-[3px]" />
74+
style={{ stroke: "#fff", ...thinIconStyle }}
75+
className="w-12 h-12 ml-[3px]"
76+
/>
4977
</div>
50-
<div className=" rounded-t-xl overflow-hidden rounded-b-none w-full h-[200px] flex flex-row items-center justify-center">
78+
79+
<div className="rounded-t-xl overflow-hidden w-full h-[200px] flex items-center justify-center">
5180
<Img
5281
className="object-cover"
5382
src={`${apiBase}/thumbnail/${file.name}`}
@@ -63,67 +92,89 @@ export const DowloadedFile: React.FC<DowloadedFileProps> = (props) => {
6392
/>
6493
</div>
6594
</div>
66-
)
67-
68-
const playButton = () => (
69-
<div className={roundButtonClasses}
70-
onClick={() => handlePlay(file.name)} >
71-
<PlayIcon
72-
className="h-6 w-6 ml-[3px] cursor-pointer"
73-
style={thinIconStyle}
74-
/>
95+
);
96+
97+
const PlayButtonControl = () => (
98+
<div className={roundButtonClasses} onClick={() => handlePlay(file.name)}>
99+
<PlayIcon className="h-6 w-6 ml-[3px]" style={thinIconStyle} />
75100
</div>
76-
)
77-
78-
const downloadButton = () => (
79-
<div className={roundButtonClasses}
80-
onClick={() => handleDownload(file.name)} >
81-
<DownloadIcon
82-
className="h-6 w-6 cursor-pointer"
83-
style={thinIconStyle}
84-
/>
101+
);
102+
103+
const DownloadButtonControl = () => (
104+
<div className={roundButtonClasses} onClick={() => handleDownload(file.name)}>
105+
<DownloadIcon className="h-6 w-6" style={thinIconStyle} />
85106
</div>
86-
)
107+
);
87108

88-
const deleteButton = () => (
109+
const DeleteButtonControl = () => (
89110
<LongPressButton
90111
onLongPress={() => handleDelete(file.name)}
91112
longPressDuration={1000}
92113
fillUpColorClass="dark:bg-red-700 bg-red-700"
93-
className={`cursor-pointer animate-color hover:border-red-700
94-
border-[1pt] border-transparent animate-all duration-500`}
114+
className="cursor-pointer animate-color hover:border-red-700 border border-transparent animate-all duration-500"
95115
>
96116
{isDeleting === file.name ? (
97-
<Trash2Icon
98-
style={thinIconStyle}
99-
className="animate-pulse" /> // Optional: visual feedback
117+
<Trash2Icon style={thinIconStyle} className="animate-pulse" />
100118
) : (
101-
<Trash2Icon
102-
style={thinIconStyle} />
119+
<Trash2Icon style={thinIconStyle} />
103120
)}
104-
105121
</LongPressButton>
106-
)
122+
);
107123

108-
const isGrid = viewType == 'grid'
109-
const isList = viewType == 'list'
124+
const MoreButtonControl = () => (
125+
<EllipsisVerticalIcon
126+
style={thinIconStyle}
127+
onClick={onToggleExpand}
128+
className={cn(roundButtonClasses, "cursor-pointer w-10 h-10")}
129+
/>
130+
);
110131

111-
return (
112-
<div className={isList ? listClasses : gridClasses}>
113-
{isGrid && ContentImage()}
114-
<div
115-
className={isList ? listNameClasses : gridNameClasses}>
116-
{file.title || file.name}
117-
<div className="text-xs">
118-
{formatDuration(file.duration)}
132+
if (!isExpanded) {
133+
return (
134+
<div className={isList ? listClasses : gridClasses}>
135+
{isGrid && <ContentImage />}
136+
137+
<div className={isList ? listNameClasses : gridNameClasses}>
138+
{file.title || file.name}
139+
<div className="text-xs">{formatDuration(file.duration)}</div>
140+
</div>
141+
142+
<div className={isList ? listButtonClasses : gridButtonClasses}>
143+
<PlayButtonControl />
144+
<DownloadButtonControl />
145+
<DeleteButtonControl />
146+
<MoreButtonControl />
119147
</div>
120148
</div>
121-
<div
122-
className={isList ? listButtonClasses : gridButtonClasses}>
123-
{playButton()}
124-
{downloadButton()}
125-
{deleteButton()}
149+
);
150+
}
151+
152+
return (
153+
<div className={cn(gridClasses, "col-span-full p-4")}>
154+
<div className="flex flex-row gap-4 items-start">
155+
156+
<div className="w-1/4 min-w-[180px]">
157+
<ContentImage />
158+
</div>
159+
160+
<div className="flex flex-col gap-2 flex-1">
161+
<div className="font-bold text-lg">{file.title || file.name}</div>
162+
<div className="text-xs">{formatDuration(file.duration)}</div>
163+
164+
{/* Placeholder expanded content */}
165+
<div className="text-sm opacity-70">
166+
Expanded view — add your future metadata/details here.
167+
</div>
168+
169+
<div className="flex flex-row gap-2 mt-2">
170+
<PlayButtonControl />
171+
<DownloadButtonControl />
172+
<DeleteButtonControl />
173+
<MoreButtonControl /> {/* This now closes it */}
174+
</div>
175+
</div>
176+
126177
</div>
127178
</div>
128-
)
129-
}
179+
);
180+
};

src/components/downloaded/downloaded-ui.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { DowloadedFile } from "@/components/downloaded/downloaded-file"
66
import { GripIcon, MenuIcon, XIcon } from "lucide-react"
77
import { Input } from "@/components/ui/input"
88
import { useAudioPlayerContext } from "@/contexts/audio-player-context-provider"
9-
import { controlIconClassName, inputResetIconClasses, thinIconStyle } from "@/lib/icon-style"
9+
import { controlIconClassName, inputResetIconClasses, thinIconStyle } from "@/lib/style"
1010
import { Icon } from "@/components/ocodo-ui/icon"
1111

1212
const getFileType = (fileName: string | undefined): 'video' | 'audio' | undefined => {
@@ -160,19 +160,27 @@ const DownloadedFilteredBySearch: FC<DownloadedFilteredBySearchProps> = ({
160160
searchQuery
161161
}) => {
162162

163+
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
164+
165+
166+
163167
const { viewType } = useDownloaded()
164168
const listClasses = "flex flex-col justify-items"
165169
const gridClasses = "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"
166170
return (
167171

168172
<div className={viewType == 'grid' ? gridClasses : listClasses} >
169173
{
170-
searchResults(searchQuery).map((file) => (
174+
searchResults(searchQuery).map((file, idx) => (
171175
<DowloadedFile
172176
handleDelete={handleDelete}
173177
handlePlay={handlePlay}
174178
handleDownload={handleDownload}
175179
isDeleting={isDeleting}
180+
isExpanded={expandedIndex === idx}
181+
onToggleExpand={() =>
182+
setExpandedIndex(expandedIndex === idx ? null : idx)
183+
}
176184
selectedFile={selectedFile}
177185
key={file.name}
178186
file={file} />

src/components/downloader/downloader-ui.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { DownloaderInput } from "@/components/downloader/downloader-input";
44
import { IndeterminateProgress } from "../ocodo-ui/indeterminate-progress";
55
import { useYtdlpContext } from "@/contexts/ytdlp-context";
66
import { Progress } from "@/components/ui/progress";
7-
import { thinIconStyle } from "@/lib/icon-style";
7+
import { thinIconStyle } from "@/lib/style";
88

99
export function DownloaderUI() {
1010
const {

src/components/downloader/youtube-search-result-box.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useState, type FC } from "react"
55
import { toast } from "sonner"
66
import { Icon } from "@/components/ocodo-ui/icon"
77
import { Img } from "react-image"
8-
import { controlIconClassName } from "@/lib/icon-style";
8+
import { controlIconClassName } from "@/lib/style";
99
import { cn } from "@/lib/utils";
1010
interface YoutubeSearchResultRowType {
1111
id: string

src/components/downloader/youtube-search-ui.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { YoutubeSearchResultBox } from "@/components/downloader/youtube-search-r
55
import { debounce } from "@/lib/debounce"
66
import { XIcon } from "lucide-react"
77
import { Icon } from "@/components/ocodo-ui/icon"
8-
import { inputResetIconClasses } from "@/lib/icon-style"
8+
import { inputResetIconClasses } from "@/lib/style"
99

1010
export const YoutubeSearchUI: FC = () => {
1111
const { setQuery, results, setResults } = useYoutubeSearchContext()

src/components/ocodo-ui/custom-audio-component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { controlIconClassName, thinIconStyle } from "@/lib/icon-style";
1+
import { controlIconClassName, thinIconStyle } from "@/lib/style";
22
import { ToggleStateButton } from "@/components/ocodo-ui/toggle-state-button";
33
import { SeekBar } from "@/components/ui/seek-bar";
44
import { useAudioPlayerContext } from "@/contexts/audio-player-context-provider";

src/components/ocodo-ui/heading.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
55
import type React from 'react'
66
import { type FC } from 'react'
77
import { UvxYtdlpIcon } from '@/components/branding/uvxytdlp-icon'
8-
import { thinIconStyle } from '@/lib/icon-style'
8+
import { thinIconStyle } from '@/lib/style'
99

1010
interface HeadingProps {
1111
title?: string

src/components/ocodo-ui/icon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { thinIconStyle } from "@/lib/icon-style";
1+
import { thinIconStyle } from "@/lib/style";
22
import { cn } from "@/lib/utils";
33
import { type LucideProps } from "lucide-react";
44
import type { FC } from "react";

src/components/ui/dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"
55
import { XIcon } from "lucide-react"
66

77
import { cn } from "@/lib/utils"
8-
import { controlIconClassName, thinIconStyle } from "@/lib/icon-style"
8+
import { controlIconClassName, thinIconStyle } from "@/lib/style"
99
import { Icon } from "@/components/ocodo-ui/icon"
1010

1111
function Dialog({

0 commit comments

Comments
 (0)