Skip to content

Commit a453379

Browse files
🚀 feat: enhance Sources component with search and reload functionality
- Integrated search functionality in `Sources.tsx`. This allows users to filter the displayed sources based on their query, improving the usability of the component. - Added a reload button with a `RotateCcw` icon from `lucide-react` to re-fetch sources. This provides a convenient way for users to refresh the list of sources without reloading the entire page. - Implemented loading state management with `sourcesLoading` state variable. It enhances user experience by indicating when sources are being loaded. - Created a new `Input` component in `ui/input.tsx`. This modular approach improves code reusability and maintainability by providing a standard input component for the application.
1 parent bedd552 commit a453379

2 files changed

Lines changed: 88 additions & 17 deletions

File tree

‎src/components/Sources.tsx‎

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { useMemo, useState } from "react";
22
import { Source, SourceType } from "../utils";
3+
import { Input } from "./ui/input";
4+
import { Button } from "./ui/button";
5+
import { RotateCcw } from "lucide-react";
36

47
async function getScreenSources() {
58
try {
@@ -20,39 +23,82 @@ export function ScreenSources({
2023
selectedSource: Source | null;
2124
setSelectedSource: (source: Source) => void;
2225
}) {
23-
const [sources, setSources] = useState<any[]>([]);
26+
const [sources, setSources] = useState<Source[]>([]);
27+
const [searchQuery, setSearchQuery] = useState("");
28+
const [sourcesLoading, setSourcesLoading] = useState(true);
2429

25-
useMemo(() => {
30+
const getSources = async () => {
31+
setSourcesLoading(true);
2632
getScreenSources().then((newSources) => {
2733
const uniqueSources = Array.from(
2834
new Set(newSources.map((source) => JSON.stringify(source)))
2935
).map((source) => JSON.parse(source));
36+
setSourcesLoading(false);
3037
setSources(uniqueSources);
3138
});
32-
}, []);
39+
};
3340

34-
if (!sources.length) return <SourcesSkeleton />;
41+
useMemo(() => {
42+
getSources();
43+
}, []);
3544

3645
return (
37-
<div className="not-prose my-6 grid grid-cols-1 gap-4 sm:grid-cols-3">
38-
{sources.map((source) => (
39-
<QuickLink
40-
key={source.id}
41-
title={source.name}
42-
applicationName={source.applicationName}
43-
imageUrl={source.thumbnail}
44-
source={source}
45-
selectedSource={selectedSource}
46-
setSelectedSource={setSelectedSource}
46+
<>
47+
<div className="flex justify-between items-center space-x-2 mt-4">
48+
<Input
49+
placeholder="Search"
50+
className="h-8 w-[150px] lg:w-[250px]"
51+
value={searchQuery}
52+
onChange={(e) => setSearchQuery(e.target.value)}
4753
/>
48-
))}
49-
</div>
54+
<Button
55+
variant="outline"
56+
size="sm"
57+
className={`h-8`}
58+
onClick={() => getSources()}
59+
>
60+
<RotateCcw
61+
className={`h-4 w-4 ${
62+
sourcesLoading ? "animate-spin-reverse" : ""
63+
}`}
64+
/>
65+
</Button>
66+
</div>
67+
{!sources.length || sourcesLoading ? (
68+
<SourcesSkeleton />
69+
) : (
70+
<div className="not-prose mb-6 mt-2 grid grid-cols-1 gap-4 sm:grid-cols-3">
71+
{sources
72+
.filter((source) => {
73+
if (!searchQuery) return true;
74+
const regex = new RegExp(searchQuery, "i");
75+
return (
76+
(source?.applicationName &&
77+
regex.test(source?.applicationName)) ||
78+
regex.test(source?.name) ||
79+
regex.test(source?.id)
80+
);
81+
})
82+
.map((source) => (
83+
<QuickLink
84+
key={source.id}
85+
title={source.name}
86+
applicationName={source.applicationName ?? source.name}
87+
imageUrl={source.thumbnail}
88+
source={source}
89+
selectedSource={selectedSource}
90+
setSelectedSource={setSelectedSource}
91+
/>
92+
))}
93+
</div>
94+
)}
95+
</>
5096
);
5197
}
5298

5399
const SourcesSkeleton = () => {
54100
return (
55-
<div className="not-prose my-6 grid grid-cols-1 gap-4 sm:grid-cols-3">
101+
<div className="not-prose mb-6 mt-2 grid grid-cols-1 gap-4 sm:grid-cols-3">
56102
{Array.from({ length: 6 }).map((_, i) => (
57103
<div
58104
key={i}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as React from "react"
2+
3+
import { cn } from "@/lib/utils"
4+
5+
export interface InputProps
6+
extends React.InputHTMLAttributes<HTMLInputElement> {}
7+
8+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
9+
({ className, type, ...props }, ref) => {
10+
return (
11+
<input
12+
type={type}
13+
className={cn(
14+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
15+
className
16+
)}
17+
ref={ref}
18+
{...props}
19+
/>
20+
)
21+
}
22+
)
23+
Input.displayName = "Input"
24+
25+
export { Input }

0 commit comments

Comments
 (0)