Skip to content

Commit d932f5a

Browse files
feat: comprehensive mobile responsiveness improvements (#76)
* feat: comprehensive mobile responsiveness improvements - Made main layout responsive with proper mobile padding and spacing - Updated Terminal component with mobile-friendly controls and sizing - Enhanced VersionDisplay with responsive layout and condensed mobile text - Improved ScriptsGrid and DownloadedScriptsTab with mobile-first design - Made CategorySidebar responsive with horizontal scroll on mobile - Fixed FilterBar styling consistency and added Lucide icons - Enhanced all modals (Settings, ScriptDetail, ExecutionMode, etc.) for mobile - Updated ServerForm and ServerList with mobile-optimized layouts - Added global CSS improvements for mobile touch targets and typography - Fixed close button placement in ScriptDetailModal to follow UI conventions - Implemented responsive breakpoints throughout the application - Added proper viewport meta tag for mobile rendering All components now provide excellent user experience across all device sizes. * fix: improve mobile terminal input handling for SSH processes - Updated mobile input controls to use up/down arrows instead of numbered buttons - Fixed WebSocket input handling to support both regular processes and pty processes (SSH) - Added comprehensive debugging logs for input handling - Added visual feedback showing when inputs are sent - Improved error handling and user feedback for input failures The mobile terminal input should now work properly with SSH-executed scripts. * debug: add comprehensive debugging for mobile terminal input - Added byte-level debugging to see exact input being sent - Added test button to verify basic input works - Enhanced server-side logging to track input processing - Improved escape sequence handling for arrow keys This will help identify why mobile inputs aren't working while keyboard works. * debug: add comprehensive server-side debugging for mobile input - Added detailed logging for mobile input processing - Added confirmation messages sent back to client - Enhanced debugging to track input flow from client to server - Added JSON string representation of inputs for better debugging This will help identify why mobile inputs aren't working while keyboard works. * debug: add WebSocket message routing debugging - Added comprehensive logging for all WebSocket messages - Added specific debugging for input action handling - Added full message object logging to identify routing issues - Enhanced input action validation logging This will help identify if input messages are reaching the server at all. * debug: add comprehensive client-side debugging for mobile input - Added detailed logging for button click events - Added WebSocket connection state debugging - Added message sending confirmation logging - Enhanced sendInput function with complete debugging This will help identify if mobile buttons are being clicked and if WebSocket messages are being sent. * debug: add WebSocket connection tracking and message debugging - Added connection ID tracking for each WebSocket connection - Added detailed logging for all incoming WebSocket messages - Added connection close and error event logging - Enhanced message parsing debugging This will help identify if mobile input messages are reaching the server and which connection they're using. * fix: correct WebSocket message format for keyboard input - Fixed keyboard input to use 'data' field instead of 'input' field - Added debugging for keyboard input to compare with mobile input - Both mobile and keyboard inputs now use consistent message format - This should fix the issue where mobile inputs weren't working The server expects 'data' field but keyboard was sending 'input' field. * debug: add WebSocket connection details for mobile vs keyboard input - Added WebSocket URL and protocol logging for both mobile and keyboard input - Added WebSocket object logging to compare connections - Enhanced debugging to identify if mobile and keyboard use different WebSocket connections This will help identify if there's a connection mismatch between mobile and keyboard input. * fix: correct WebSocket message format to use 'input' field - Reverted both mobile and keyboard input to use 'input' field instead of 'data' - Updated server to expect 'input' field in WebSocket messages - Fixed server-side logging to use correct field names - This should restore both keyboard and mobile input functionality The server was actually expecting 'input' field, not 'data' field. * feat: add left/right arrow buttons to mobile terminal input - Added ChevronLeft and ChevronRight icons to imports - Added left/right navigation buttons alongside up/down buttons - Left button sends \x1b[D (ANSI escape sequence for left arrow) - Right button sends \x1b[C (ANSI escape sequence for right arrow) - Updated visual feedback to show 'Left' and 'Right' for arrow inputs - Mobile users now have full directional navigation: up, down, left, right This completes the mobile terminal navigation controls for touch devices. * feat: add spacebar button and clean up mobile terminal controls - Added spacebar button to mobile input controls - Removed 'Yes (y)' and 'Test (1)' buttons to simplify interface - Changed action buttons from 3-column to 2-column grid (Enter, Space) - Updated visual feedback to show 'Space' for spacebar input - Mobile controls now focus on essential navigation and input This streamlines the mobile terminal interface with only the most useful controls. * feat: add backspace button to mobile terminal controls - Added backspace button (⌫ Backspace) to action buttons - Sends \b character for backspace functionality - Changed action buttons from 2-column to 3-column grid - Updated visual feedback to show 'Backspace' for backspace input - Mobile users now have complete text editing capabilities This completes the essential mobile terminal input controls with navigation, text input, and editing functions. * feat: improve mobile terminal scaling and responsiveness - Reduced font size from 14px to 10px on mobile devices (< 768px width) - Set mobile-specific terminal dimensions (20 rows, 60 cols) for better fit - Reduced mobile terminal height from 20rem to 16rem (256px min-height) - Added responsive resize listener to adjust terminal size on orientation changes - Improved mobile terminal display to prevent cramped text and odd appearance - Better balance between terminal content and mobile input controls This makes the terminal much more readable and usable on mobile devices. * fix: improve ANSI escape sequence handling for whiptail dialogs - Added better ANSI handling configuration to terminal - Added detection and logging for screen clearing sequences (\x1b[2J, \x1b[H\x1b[2J) - Added detection and logging for cursor positioning sequences - Enabled allowProposedApi for better terminal compatibility - Added debugging to identify when clear screen operations occur This should fix the issue where whiptail dialogs duplicate content and don't properly clear the screen on mobile input. * debug: add whiptail/dialog detection and logging - Added detection for whiptail and dialog output in terminal messages - Added logging to track when whiptail content is being processed - This will help identify if the issue is with ANSI sequence processing - Console logs will show when clear screen, cursor positioning, and whiptail content is detected This debugging will help identify the root cause of the terminal rerendering issue. * fix: force screen clear on cursor positioning to prevent whiptail duplication - Modified output handling to force screen clear (\x1b[2J\x1b[H) when cursor positioning is detected - Removed whiptail-specific detection in favor of broader cursor positioning approach - This should prevent content duplication when whiptail redraws its interface - Cursor positioning sequences often indicate a full screen redraw is intended This aggressive approach should finally fix the terminal rerendering issue. * debug: add comprehensive output debugging and try terminal.clear() - Added detailed logging for all output messages including length, preview, and ANSI detection - Changed cursor positioning handling to use xtermRef.current.clear() for more aggressive clearing - This will help identify exactly what data is being received and how it's being processed - The terminal.clear() method should completely reset the terminal buffer This debugging will help us understand why the duplication is still occurring. * debug: add whiptail session detection and enhanced debugging - Added inWhiptailSession state to track when we're in a whiptail dialog - Added detection for whiptail/dialog content to set session flag - Enhanced output debugging with comprehensive logging - Added aggressive terminal clearing specifically for whiptail sessions - Reset whiptail session when script ends - Set explicit terminal dimensions (cols/rows) for better behavior This should provide much more detailed debugging information and better handling of whiptail sessions. * feat: improve whiptail centering on mobile devices - Reduced mobile terminal dimensions to 50 cols x 18 rows for better whiptail centering - Reduced mobile terminal height from 16rem to 14rem (224px min-height) - Added isMobile state to component level for consistent mobile detection - Added horizontal padding on mobile to help center terminal content - Smaller terminal dimensions should help whiptail dialogs appear more centered This should improve the visual positioning of whiptail dialogs on mobile devices. * feat: improve whiptail horizontal centering on mobile - Reduced mobile terminal dimensions to 40 cols x 16 rows for better centering - Added mobile-terminal CSS class with flex centering - Added CSS rules to center xterm content horizontally on mobile - Added transform translateX for fine-tuning horizontal position - Increased horizontal padding to 2rem on mobile This should better center the whiptail dialog horizontally on mobile devices. * fix: resolve text wrapping and overflow issues in mobile terminal - Increased mobile terminal dimensions to 60 cols x 20 rows to prevent text cutoff - Increased mobile terminal height back to 16rem (256px) for better content display - Added overflow: hidden to mobile terminal CSS to prevent content overflow - Added width and max-width constraints to xterm elements - Reduced horizontal padding and transform to better fit content - Cleaned up duplicate terminal configuration options This should fix the weird text wrapping and cutoff issues on mobile devices. * fix: try auto-fit approach to resolve mobile terminal text wrapping - Removed fixed terminal dimensions on mobile to let it auto-fit - Added double fit calls for mobile to ensure proper sizing - Removed restrictive CSS overflow and transform rules - Simplified terminal container styling for better auto-fitting - Let xterm.js handle the sizing automatically on mobile This approach should allow the terminal to properly fit the content without text cutoff. * fix: improve mobile terminal centering with specific dimensions - Set mobile terminal to 45 cols x 18 rows for better whiptail dialog fit - Added padding and transform to better center content on mobile - Used flex centering in terminal container for mobile - Added overflow hidden to prevent text cutoff - More aggressive centering approach for mobile devices This should better center the whiptail dialog and prevent text cutoff on mobile. * feat: implement virtual terminal overflow approach for mobile whiptail - Increased mobile virtual terminal to 80 cols x 30 rows (larger than display) - Let virtual terminal overflow and center the whiptail dialog in viewport - Added overflow: hidden to container to hide overflow content - Centered viewport to show middle portion of virtual terminal - This approach lets whiptail render at full size while showing only the center This should properly center the whiptail dialog without text wrapping or cutoff issues. * revert: simplify mobile terminal approach and reduce font size - Reverted to simpler 50 cols x 20 rows terminal dimensions - Reduced mobile font size from 10px to 8px for better fit - Simplified CSS to basic flex centering without complex overflow handling - Added multiple fit calls for mobile to ensure proper terminal sizing - Removed complex virtual terminal overflow approach that wasn't working This should provide a more stable and predictable mobile terminal display. * feat: reduce mobile terminal font size and dimensions for better fit - Reduced mobile font size from 8px to 6px for even smaller text - Reduced mobile terminal dimensions to 45 cols x 18 rows - This should provide more room for whiptail dialog content on mobile - Smaller font and dimensions should prevent text cutoff and wrapping This should finally get the whiptail dialog to fit properly on mobile devices. * feat: increase mobile font size and fix whiptail duplication - Increased mobile font size from 6px to 7px for slightly larger text - Fixed whiptail duplication by adding delay after terminal clear - Added setTimeout to ensure clear is processed before writing new content - This should prevent the weird lines/duplication while keeping good fit This should give a good balance between readability and fit while preventing duplication. * fix: implement more aggressive terminal clearing for whiptail - Added multiple clear operations: clear(), \x1b[2J\x1b[H, \x1b[3J, \x1b[2J - Added scrollback buffer clearing with \x1b[3J - Increased delay from 10ms to 50ms for better processing - Added double clear() calls to force terminal buffer clearing - This should finally eliminate the duplication lines in whiptail dialogs This aggressive approach should completely clear the terminal before redrawing whiptail content. * fix: implement terminal reset approach for whiptail duplication - Added immediate clearing when whiptail session is detected - Implemented terminal.reset() method for complete terminal state reset - Added longer 100ms delay after reset to ensure proper processing - Clear terminal immediately when whiptail content is first detected - This nuclear approach should completely eliminate duplication issues This should finally solve the persistent duplication problem by completely resetting the terminal state. * cleanup: remove all debug logging from terminal component - Removed all console.log statements from output handling - Removed debug logging from mobile input functions - Removed debug logging from keyboard input handler - Removed debug logging from all mobile button click handlers - Simplified button onClick handlers to direct function calls - Kept all functionality while removing debugging noise The terminal now has clean, production-ready code without debug output. * feat: make InstalledScriptsTab mobile-friendly with responsive layout - Created ScriptInstallationCard component for mobile view - Added responsive layout: cards on mobile (< md), table on desktop (>= md) - Made filters section mobile-friendly with stacked layout - Improved add script form with responsive button layout - Cards show all script details in a clean, touch-friendly format - Maintained all existing functionality (edit, update, delete) - Used proper Tailwind breakpoints for seamless responsive behavior The installed scripts tab now provides an optimal experience on both mobile and desktop devices. * fix: resolve React hooks dependency warnings in Terminal component - Added missing 'inWhiptailSession' dependency to useCallback - Fixed ref cleanup issue by storing terminalRef.current in variable - Added missing 'isMobile' dependency to useEffect - Improved nullish coalescing in ScriptInstallationCard (|| to ??) - All React hooks warnings resolved, build passes cleanly The Terminal component now follows React best practices for hooks dependencies.
1 parent 39a572a commit d932f5a

19 files changed

+1123
-481
lines changed

src/app/_components/CategorySidebar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ export function CategorySidebar({
196196

197197
return (
198198
<div className={`bg-card rounded-lg shadow-md border border-border transition-all duration-300 ${
199-
isCollapsed ? 'w-16' : 'w-80'
199+
isCollapsed ? 'w-16' : 'w-full lg:w-80'
200200
}`}>
201201
{/* Header */}
202202
<div className="flex items-center justify-between p-4 border-b border-border">
@@ -292,7 +292,7 @@ export function CategorySidebar({
292292

293293
{/* Collapsed state - show only icons with counters and tooltips */}
294294
{isCollapsed && (
295-
<div className="p-2 flex flex-col space-y-2">
295+
<div className="p-2 flex flex-row lg:flex-col space-x-2 lg:space-x-0 lg:space-y-2 overflow-x-auto lg:overflow-x-visible">
296296
{/* "All Categories" option */}
297297
<div className="group relative">
298298
<button
@@ -317,7 +317,7 @@ export function CategorySidebar({
317317
</button>
318318

319319
{/* Tooltip */}
320-
<div className="absolute left-full ml-2 top-1/2 transform -translate-y-1/2 bg-gray-900 text-white text-sm px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-50">
320+
<div className="absolute left-full ml-2 top-1/2 transform -translate-y-1/2 bg-gray-900 text-white text-sm px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-50 hidden lg:block">
321321
All Categories ({totalScripts})
322322
</div>
323323
</div>
@@ -350,7 +350,7 @@ export function CategorySidebar({
350350
</button>
351351

352352
{/* Tooltip */}
353-
<div className="absolute left-full ml-2 top-1/2 transform -translate-y-1/2 bg-gray-900 text-white text-sm px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-50">
353+
<div className="absolute left-full ml-2 top-1/2 transform -translate-y-1/2 bg-gray-900 text-white text-sm px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-50 hidden lg:block">
354354
{category} ({count})
355355
</div>
356356
</div>

src/app/_components/DiffViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function DiffViewer({ scriptSlug, filePath, isOpen, onClose }: DiffViewer
6969
className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center p-4 z-50"
7070
onClick={handleBackdropClick}
7171
>
72-
<div className="bg-card rounded-lg shadow-xl max-w-6xl w-full max-h-[90vh] overflow-hidden border border-border">
72+
<div className="bg-card rounded-lg shadow-xl max-w-6xl w-full max-h-[90vh] overflow-hidden border border-border mx-4 sm:mx-0">
7373
{/* Header */}
7474
<div className="flex items-center justify-between p-4 border-b border-border">
7575
<div>

src/app/_components/DownloadedScriptsTab.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,9 @@ export function DownloadedScriptsTab() {
320320
</div>
321321
</div>
322322

323-
<div className="flex gap-6">
323+
<div className="flex flex-col lg:flex-row gap-4 lg:gap-6">
324324
{/* Category Sidebar */}
325-
<div className="flex-shrink-0">
325+
<div className="flex-shrink-0 order-2 lg:order-1">
326326
<CategorySidebar
327327
categories={categories}
328328
categoryCounts={categoryCounts}
@@ -333,7 +333,7 @@ export function DownloadedScriptsTab() {
333333
</div>
334334

335335
{/* Main Content */}
336-
<div className="flex-1 min-w-0" ref={gridRef}>
336+
<div className="flex-1 min-w-0 order-1 lg:order-2" ref={gridRef}>
337337
{/* Enhanced Filter Bar */}
338338
<FilterBar
339339
filters={filters}

src/app/_components/ExecutionModeModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ export function ExecutionModeModal({ isOpen, onClose, onExecute, scriptName }: E
6161
if (!isOpen) return null;
6262

6363
return (
64-
<div className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50">
65-
<div className="bg-card rounded-lg shadow-xl max-w-md w-full mx-4 border border-border">
64+
<div className="fixed inset-0 backdrop-blur-sm bg-black/50 flex items-center justify-center z-50 p-4">
65+
<div className="bg-card rounded-lg shadow-xl max-w-md w-full border border-border">
6666
{/* Header */}
6767
<div className="flex items-center justify-between p-6 border-b border-border">
6868
<h2 className="text-xl font-bold text-foreground">Execution Mode</h2>

src/app/_components/FilterBar.tsx

Lines changed: 130 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import React, { useState } from "react";
44
import { Button } from "./ui/button";
5-
import { Package, Monitor, Wrench, Server, FileText, Calendar } from "lucide-react";
5+
import { Package, Monitor, Wrench, Server, FileText, Calendar, RefreshCw, Filter } from "lucide-react";
66

77
export interface FilterState {
88
searchQuery: string;
@@ -35,6 +35,7 @@ export function FilterBar({
3535
updatableCount = 0,
3636
}: FilterBarProps) {
3737
const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false);
38+
const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false);
3839

3940
const updateFilters = (updates: Partial<FilterState>) => {
4041
onFiltersChange({ ...filters, ...updates });
@@ -76,10 +77,10 @@ export function FilterBar({
7677
};
7778

7879
return (
79-
<div className="mb-6 rounded-lg border border-border bg-card p-6 shadow-sm">
80+
<div className="mb-6 rounded-lg border border-border bg-card p-4 sm:p-6 shadow-sm">
8081
{/* Search Bar */}
8182
<div className="mb-4">
82-
<div className="relative max-w-md">
83+
<div className="relative max-w-md w-full">
8384
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
8485
<svg
8586
className="h-5 w-5 text-muted-foreground"
@@ -128,7 +129,7 @@ export function FilterBar({
128129
</div>
129130

130131
{/* Filter Buttons */}
131-
<div className="mb-4 flex flex-wrap gap-3">
132+
<div className="mb-4 flex flex-col sm:flex-row flex-wrap gap-2 sm:gap-3">
132133
{/* Updateable Filter */}
133134
<Button
134135
onClick={() => {
@@ -142,29 +143,31 @@ export function FilterBar({
142143
}}
143144
variant="outline"
144145
size="default"
145-
className={`${
146-
filters.showUpdatable === null
147-
? "bg-muted text-muted-foreground hover:bg-accent"
148-
: filters.showUpdatable === true
149-
? "border border-green-500/20 bg-green-500/10 text-green-400"
150-
: "border border-destructive/20 bg-destructive/10 text-destructive"
151-
}`}
146+
className={`w-full sm:w-auto flex items-center justify-center space-x-2 ${
147+
filters.showUpdatable === null
148+
? "bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground"
149+
: filters.showUpdatable === true
150+
? "border border-green-500/20 bg-green-500/10 text-green-400"
151+
: "border border-destructive/20 bg-destructive/10 text-destructive"
152+
}`}
152153
>
153-
{getUpdatableButtonText()}
154+
<RefreshCw className="h-4 w-4" />
155+
<span>{getUpdatableButtonText()}</span>
154156
</Button>
155157

156158
{/* Type Dropdown */}
157-
<div className="relative">
159+
<div className="relative w-full sm:w-auto">
158160
<Button
159161
onClick={() => setIsTypeDropdownOpen(!isTypeDropdownOpen)}
160162
variant="outline"
161163
size="default"
162-
className={`flex items-center space-x-2 ${
164+
className={`w-full flex items-center justify-center space-x-2 ${
163165
filters.selectedTypes.length === 0
164-
? "bg-muted text-muted-foreground hover:bg-accent"
166+
? "bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground"
165167
: "border border-primary/20 bg-primary/10 text-primary"
166168
}`}
167169
>
170+
<Filter className="h-4 w-4" />
168171
<span>{getTypeButtonText()}</span>
169172
<svg
170173
className={`h-4 w-4 transition-transform ${isTypeDropdownOpen ? "rotate-180" : ""}`}
@@ -237,85 +240,122 @@ export function FilterBar({
237240
)}
238241
</div>
239242

240-
{/* Sort Options */}
241-
<div className="flex items-center space-x-2">
242-
{/* Sort By Dropdown */}
243-
<div className="relative inline-flex items-center">
244-
<select
245-
value={filters.sortBy}
246-
onChange={(e) =>
247-
updateFilters({ sortBy: e.target.value as "name" | "created" })
248-
}
249-
className="rounded-lg border border-input bg-background pl-9 pr-3 py-2 text-sm text-foreground focus:ring-2 focus:ring-primary focus:outline-none appearance-none"
250-
>
251-
<option value="name">By Name</option>
252-
<option value="created">By Created Date</option>
253-
</select>
254-
<div className="absolute left-2 pointer-events-none">
255-
{filters.sortBy === "name" ? (
256-
<FileText className="h-4 w-4 text-muted-foreground" />
257-
) : (
258-
<Calendar className="h-4 w-4 text-muted-foreground" />
259-
)}
260-
</div>
261-
</div>
262-
263-
{/* Sort Order Button */}
243+
{/* Sort By Dropdown */}
244+
<div className="relative w-full sm:w-auto">
264245
<Button
265-
onClick={() =>
266-
updateFilters({
267-
sortOrder: filters.sortOrder === "asc" ? "desc" : "asc",
268-
})
269-
}
246+
onClick={() => setIsSortDropdownOpen(!isSortDropdownOpen)}
270247
variant="outline"
271248
size="default"
272-
className="flex items-center space-x-1 bg-muted text-muted-foreground hover:bg-accent"
249+
className="w-full sm:w-auto flex items-center justify-center space-x-2 bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground"
273250
>
274-
{filters.sortOrder === "asc" ? (
275-
<>
276-
<svg
277-
className="h-4 w-4"
278-
fill="none"
279-
stroke="currentColor"
280-
viewBox="0 0 24 24"
281-
>
282-
<path
283-
strokeLinecap="round"
284-
strokeLinejoin="round"
285-
strokeWidth={2}
286-
d="M7 11l5-5m0 0l5 5m-5-5v12"
287-
/>
288-
</svg>
289-
<span>
290-
{filters.sortBy === "created" ? "Oldest First" : "A-Z"}
291-
</span>
292-
</>
251+
{filters.sortBy === "name" ? (
252+
<FileText className="h-4 w-4" />
293253
) : (
294-
<>
295-
<svg
296-
className="h-4 w-4"
297-
fill="none"
298-
stroke="currentColor"
299-
viewBox="0 0 24 24"
300-
>
301-
<path
302-
strokeLinecap="round"
303-
strokeLinejoin="round"
304-
strokeWidth={2}
305-
d="M17 13l-5 5m0 0l-5-5m5 5V6"
306-
/>
307-
</svg>
308-
<span>
309-
{filters.sortBy === "created" ? "Newest First" : "Z-A"}
310-
</span>
311-
</>
254+
<Calendar className="h-4 w-4" />
312255
)}
256+
<span>{filters.sortBy === "name" ? "By Name" : "By Created Date"}</span>
257+
<svg
258+
className={`h-4 w-4 transition-transform ${isSortDropdownOpen ? "rotate-180" : ""}`}
259+
fill="none"
260+
stroke="currentColor"
261+
viewBox="0 0 24 24"
262+
>
263+
<path
264+
strokeLinecap="round"
265+
strokeLinejoin="round"
266+
strokeWidth={2}
267+
d="M19 9l-7 7-7-7"
268+
/>
269+
</svg>
313270
</Button>
271+
272+
{isSortDropdownOpen && (
273+
<div className="absolute top-full left-0 z-10 mt-1 w-full sm:w-48 rounded-lg border border-border bg-card shadow-lg">
274+
<div className="p-2">
275+
<button
276+
onClick={() => {
277+
updateFilters({ sortBy: "name" });
278+
setIsSortDropdownOpen(false);
279+
}}
280+
className={`w-full flex items-center space-x-3 rounded-md px-3 py-2 text-left hover:bg-accent ${
281+
filters.sortBy === "name" ? "bg-primary/10 text-primary" : "text-muted-foreground"
282+
}`}
283+
>
284+
<FileText className="h-4 w-4" />
285+
<span className="text-sm">By Name</span>
286+
</button>
287+
<button
288+
onClick={() => {
289+
updateFilters({ sortBy: "created" });
290+
setIsSortDropdownOpen(false);
291+
}}
292+
className={`w-full flex items-center space-x-3 rounded-md px-3 py-2 text-left hover:bg-accent ${
293+
filters.sortBy === "created" ? "bg-primary/10 text-primary" : "text-muted-foreground"
294+
}`}
295+
>
296+
<Calendar className="h-4 w-4" />
297+
<span className="text-sm">By Created Date</span>
298+
</button>
299+
</div>
300+
</div>
301+
)}
314302
</div>
303+
304+
{/* Sort Order Button */}
305+
<Button
306+
onClick={() =>
307+
updateFilters({
308+
sortOrder: filters.sortOrder === "asc" ? "desc" : "asc",
309+
})
310+
}
311+
variant="outline"
312+
size="default"
313+
className="w-full sm:w-auto flex items-center justify-center space-x-1 bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground"
314+
>
315+
{filters.sortOrder === "asc" ? (
316+
<>
317+
<svg
318+
className="h-4 w-4"
319+
fill="none"
320+
stroke="currentColor"
321+
viewBox="0 0 24 24"
322+
>
323+
<path
324+
strokeLinecap="round"
325+
strokeLinejoin="round"
326+
strokeWidth={2}
327+
d="M7 11l5-5m0 0l5 5m-5-5v12"
328+
/>
329+
</svg>
330+
<span>
331+
{filters.sortBy === "created" ? "Oldest First" : "A-Z"}
332+
</span>
333+
</>
334+
) : (
335+
<>
336+
<svg
337+
className="h-4 w-4"
338+
fill="none"
339+
stroke="currentColor"
340+
viewBox="0 0 24 24"
341+
>
342+
<path
343+
strokeLinecap="round"
344+
strokeLinejoin="round"
345+
strokeWidth={2}
346+
d="M17 13l-5 5m0 0l-5-5m5 5V6"
347+
/>
348+
</svg>
349+
<span>
350+
{filters.sortBy === "created" ? "Newest First" : "Z-A"}
351+
</span>
352+
</>
353+
)}
354+
</Button>
315355
</div>
316356

317357
{/* Filter Summary and Clear All */}
318-
<div className="flex items-center justify-between">
358+
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
319359
<div className="text-sm text-muted-foreground">
320360
{filteredCount === totalScripts ? (
321361
<span>Showing all {totalScripts} scripts</span>
@@ -336,7 +376,7 @@ export function FilterBar({
336376
onClick={clearAllFilters}
337377
variant="ghost"
338378
size="sm"
339-
className="flex items-center space-x-1 text-red-600 hover:bg-red-50 hover:text-red-800"
379+
className="flex items-center space-x-1 text-red-600 hover:bg-red-50 hover:text-red-800 w-full sm:w-auto justify-center sm:justify-start"
340380
>
341381
<svg
342382
className="h-4 w-4"
@@ -356,11 +396,14 @@ export function FilterBar({
356396
)}
357397
</div>
358398

359-
{/* Click outside to close dropdown */}
360-
{isTypeDropdownOpen && (
399+
{/* Click outside to close dropdowns */}
400+
{(isTypeDropdownOpen || isSortDropdownOpen) && (
361401
<div
362402
className="fixed inset-0 z-0"
363-
onClick={() => setIsTypeDropdownOpen(false)}
403+
onClick={() => {
404+
setIsTypeDropdownOpen(false);
405+
setIsSortDropdownOpen(false);
406+
}}
364407
/>
365408
)}
366409
</div>

0 commit comments

Comments
 (0)