Skip to content

Commit 21f723b

Browse files
Merge pull request #281 from community-scripts/fix/270
Add minimize buttons to FilterBar and Newest Scripts sections
2 parents 61904f2 + 45ba67c commit 21f723b

File tree

2 files changed

+123
-68
lines changed

2 files changed

+123
-68
lines changed

src/app/_components/FilterBar.tsx

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function FilterBar({
4141
}: FilterBarProps) {
4242
const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false);
4343
const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false);
44+
const [isMinimized, setIsMinimized] = useState(false);
4445

4546
const updateFilters = (updates: Partial<FilterState>) => {
4647
onFiltersChange({ ...filters, ...updates });
@@ -98,44 +99,17 @@ export function FilterBar({
9899
{!isLoadingFilters && (
99100
<div className="mb-4 flex items-center justify-between">
100101
<h3 className="text-lg font-medium text-foreground">Filter Scripts</h3>
101-
<ContextualHelpIcon section="available-scripts" tooltip="Help with filtering and searching" />
102-
</div>
103-
)}
104-
105-
{/* Search Bar */}
106-
<div className="mb-4">
107-
<div className="relative max-w-md w-full">
108-
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
109-
<svg
110-
className="h-5 w-5 text-muted-foreground"
111-
fill="none"
112-
stroke="currentColor"
113-
viewBox="0 0 24 24"
114-
>
115-
<path
116-
strokeLinecap="round"
117-
strokeLinejoin="round"
118-
strokeWidth={2}
119-
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
120-
/>
121-
</svg>
122-
</div>
123-
<input
124-
type="text"
125-
placeholder="Search scripts..."
126-
value={filters.searchQuery}
127-
onChange={(e) => updateFilters({ searchQuery: e.target.value })}
128-
className="block w-full rounded-lg border border-input bg-background py-3 pr-10 pl-10 text-sm leading-5 text-foreground placeholder-muted-foreground focus:border-primary focus:placeholder-muted-foreground focus:ring-2 focus:ring-primary focus:outline-none"
129-
/>
130-
{filters.searchQuery && (
102+
<div className="flex items-center gap-2">
103+
<ContextualHelpIcon section="available-scripts" tooltip="Help with filtering and searching" />
131104
<Button
132-
onClick={() => updateFilters({ searchQuery: "" })}
105+
onClick={() => setIsMinimized(!isMinimized)}
133106
variant="ghost"
134107
size="icon"
135-
className="absolute inset-y-0 right-0 pr-3 text-muted-foreground hover:text-foreground"
108+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
109+
title={isMinimized ? "Expand filters" : "Minimize filters"}
136110
>
137111
<svg
138-
className="h-5 w-5"
112+
className={`h-4 w-4 transition-transform ${isMinimized ? "" : "rotate-180"}`}
139113
fill="none"
140114
stroke="currentColor"
141115
viewBox="0 0 24 24"
@@ -144,16 +118,69 @@ export function FilterBar({
144118
strokeLinecap="round"
145119
strokeLinejoin="round"
146120
strokeWidth={2}
147-
d="M6 18L18 6M6 6l12 12"
121+
d="M5 15l7-7 7 7"
148122
/>
149123
</svg>
150124
</Button>
151-
)}
125+
</div>
152126
</div>
153-
</div>
127+
)}
154128

155-
{/* Filter Buttons */}
156-
<div className="mb-4 flex flex-col sm:flex-row flex-wrap gap-2 sm:gap-3">
129+
{/* Filter Content - Conditionally rendered based on minimized state */}
130+
{!isMinimized && !isLoadingFilters && (
131+
<>
132+
{/* Search Bar */}
133+
<div className="mb-4">
134+
<div className="relative max-w-md w-full">
135+
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
136+
<svg
137+
className="h-5 w-5 text-muted-foreground"
138+
fill="none"
139+
stroke="currentColor"
140+
viewBox="0 0 24 24"
141+
>
142+
<path
143+
strokeLinecap="round"
144+
strokeLinejoin="round"
145+
strokeWidth={2}
146+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
147+
/>
148+
</svg>
149+
</div>
150+
<input
151+
type="text"
152+
placeholder="Search scripts..."
153+
value={filters.searchQuery}
154+
onChange={(e) => updateFilters({ searchQuery: e.target.value })}
155+
className="block w-full rounded-lg border border-input bg-background py-3 pr-10 pl-10 text-sm leading-5 text-foreground placeholder-muted-foreground focus:border-primary focus:placeholder-muted-foreground focus:ring-2 focus:ring-primary focus:outline-none"
156+
/>
157+
{filters.searchQuery && (
158+
<Button
159+
onClick={() => updateFilters({ searchQuery: "" })}
160+
variant="ghost"
161+
size="icon"
162+
className="absolute inset-y-0 right-0 pr-3 text-muted-foreground hover:text-foreground"
163+
>
164+
<svg
165+
className="h-5 w-5"
166+
fill="none"
167+
stroke="currentColor"
168+
viewBox="0 0 24 24"
169+
>
170+
<path
171+
strokeLinecap="round"
172+
strokeLinejoin="round"
173+
strokeWidth={2}
174+
d="M6 18L18 6M6 6l12 12"
175+
/>
176+
</svg>
177+
</Button>
178+
)}
179+
</div>
180+
</div>
181+
182+
{/* Filter Buttons */}
183+
<div className="mb-4 flex flex-col sm:flex-row flex-wrap gap-2 sm:gap-3">
157184
{/* Updateable Filter */}
158185
<Button
159186
onClick={() => {
@@ -431,6 +458,8 @@ export function FilterBar({
431458
</Button>
432459
)}
433460
</div>
461+
</>
462+
)}
434463

435464
{/* Click outside to close dropdowns */}
436465
{(isTypeDropdownOpen || isSortDropdownOpen) && (

src/app/_components/ScriptsGrid.tsx

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
3434
});
3535
const [saveFiltersEnabled, setSaveFiltersEnabled] = useState(false);
3636
const [isLoadingFilters, setIsLoadingFilters] = useState(true);
37+
const [isNewestMinimized, setIsNewestMinimized] = useState(false);
3738
const gridRef = useRef<HTMLDivElement>(null);
3839

3940
const { data: scriptCardsData, isLoading: githubLoading, error: githubError, refetch } = api.scripts.getScriptCardsWithCategories.useQuery();
@@ -689,48 +690,73 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
689690
onViewModeChange={setViewMode}
690691
/>
691692

692-
{/* Newest Scripts Carousel - Always show when there are newest scripts */}
693-
{newestScripts.length > 0 && (
693+
{/* Newest Scripts Carousel - Only show when no search, filters, or category is active */}
694+
{newestScripts.length > 0 && !hasActiveFilters && !selectedCategory && (
694695
<div className="mb-8">
695696
<div className="bg-card border-l-4 border-l-primary border border-border rounded-lg p-6 shadow-lg">
696697
<div className="flex items-center justify-between mb-4">
697698
<h2 className="text-xl font-semibold text-foreground flex items-center gap-2">
698699
<Clock className="h-6 w-6 text-primary" />
699700
Newest Scripts
700701
</h2>
701-
<span className="text-sm text-muted-foreground">
702-
{newestScripts.length} recently added
703-
</span>
702+
<div className="flex items-center gap-2">
703+
<span className="text-sm text-muted-foreground">
704+
{newestScripts.length} recently added
705+
</span>
706+
<Button
707+
onClick={() => setIsNewestMinimized(!isNewestMinimized)}
708+
variant="ghost"
709+
size="icon"
710+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
711+
title={isNewestMinimized ? "Expand newest scripts" : "Minimize newest scripts"}
712+
>
713+
<svg
714+
className={`h-4 w-4 transition-transform ${isNewestMinimized ? "" : "rotate-180"}`}
715+
fill="none"
716+
stroke="currentColor"
717+
viewBox="0 0 24 24"
718+
>
719+
<path
720+
strokeLinecap="round"
721+
strokeLinejoin="round"
722+
strokeWidth={2}
723+
d="M5 15l7-7 7 7"
724+
/>
725+
</svg>
726+
</Button>
727+
</div>
704728
</div>
705729

706-
<div className="overflow-x-auto scrollbar-thin scrollbar-thumb-gray-300 dark:scrollbar-thumb-gray-600 scrollbar-track-transparent">
707-
<div className="flex gap-4 pb-2" style={{ minWidth: 'max-content' }}>
708-
{newestScripts.map((script, index) => {
709-
if (!script || typeof script !== 'object') {
710-
return null;
711-
}
712-
713-
const uniqueKey = `newest-${script.slug ?? 'unknown'}-${script.name ?? 'unnamed'}-${index}`;
714-
715-
return (
716-
<div key={uniqueKey} className="flex-shrink-0 w-64 sm:w-72 md:w-80">
717-
<div className="relative">
718-
<ScriptCard
719-
script={script}
720-
onClick={handleCardClick}
721-
isSelected={selectedSlugs.has(script.slug ?? '')}
722-
onToggleSelect={toggleScriptSelection}
723-
/>
724-
{/* NEW badge */}
725-
<div className="absolute top-2 right-2 bg-success text-success-foreground text-xs font-semibold px-2 py-1 rounded-md shadow-md z-10">
726-
NEW
730+
{!isNewestMinimized && (
731+
<div className="overflow-x-auto scrollbar-thin scrollbar-thumb-gray-300 dark:scrollbar-thumb-gray-600 scrollbar-track-transparent">
732+
<div className="flex gap-4 pb-2" style={{ minWidth: 'max-content' }}>
733+
{newestScripts.map((script, index) => {
734+
if (!script || typeof script !== 'object') {
735+
return null;
736+
}
737+
738+
const uniqueKey = `newest-${script.slug ?? 'unknown'}-${script.name ?? 'unnamed'}-${index}`;
739+
740+
return (
741+
<div key={uniqueKey} className="flex-shrink-0 w-64 sm:w-72 md:w-80">
742+
<div className="relative">
743+
<ScriptCard
744+
script={script}
745+
onClick={handleCardClick}
746+
isSelected={selectedSlugs.has(script.slug ?? '')}
747+
onToggleSelect={toggleScriptSelection}
748+
/>
749+
{/* NEW badge */}
750+
<div className="absolute top-2 right-2 bg-success text-success-foreground text-xs font-semibold px-2 py-1 rounded-md shadow-md z-10">
751+
NEW
752+
</div>
727753
</div>
728754
</div>
729-
</div>
730-
);
731-
})}
755+
);
756+
})}
757+
</div>
732758
</div>
733-
</div>
759+
)}
734760
</div>
735761
</div>
736762
)}

0 commit comments

Comments
 (0)