feat: implement responsive design enhancements
- Add touch-optimized tap targets (44x44px minimum for iOS) - Enhance mobile responsiveness across all components - Improve button sizing and spacing for touch interactions - Add responsive text sizing and layout adjustments - Implement mobile-first modal behavior (bottom sheet on mobile) - Enhance form controls with better touch targets - Add active states for better touch feedback - Improve grid layouts for various screen sizes - Add smooth scrolling and better focus states - Optimize FilterPanel for mobile with grid layouts - Enhance all interactive elements with tap-target classes Requirements: 7.1, 7.2
This commit is contained in:
@@ -92,27 +92,27 @@ export default function FilterPanel({
|
||||
{/* Header */}
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="w-full flex items-center justify-between p-4 hover:bg-gray-50 transition-colors"
|
||||
className="w-full flex items-center justify-between p-4 sm:p-5 hover:bg-gray-50 active:bg-gray-100 transition-colors tap-target"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter size={20} className="text-gray-600" />
|
||||
<span className="font-medium text-gray-900">Filters & Sort</span>
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<Filter size={20} className="text-gray-600 flex-shrink-0" />
|
||||
<span className="font-medium text-gray-900 text-sm sm:text-base">Filters & Sort</span>
|
||||
{hasActiveFilters && (
|
||||
<span className="bg-blue-100 text-blue-700 text-xs font-medium px-2 py-1 rounded">
|
||||
<span className="bg-blue-100 text-blue-700 text-xs font-medium px-2 py-1 rounded whitespace-nowrap">
|
||||
Active
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isExpanded ? (
|
||||
<ChevronUp size={20} className="text-gray-400" />
|
||||
<ChevronUp size={20} className="text-gray-400 flex-shrink-0" />
|
||||
) : (
|
||||
<ChevronDown size={20} className="text-gray-400" />
|
||||
<ChevronDown size={20} className="text-gray-400 flex-shrink-0" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Filter Controls */}
|
||||
{isExpanded && (
|
||||
<div className="p-4 border-t border-gray-200 space-y-4">
|
||||
<div className="p-4 sm:p-5 border-t border-gray-200 space-y-4 sm:space-y-5">
|
||||
{/* Sort Controls */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
@@ -122,7 +122,7 @@ export default function FilterPanel({
|
||||
<select
|
||||
value={filters.sortBy}
|
||||
onChange={(e) => handleSortByChange(e.target.value as FilterState['sortBy'])}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="flex-1 px-3 sm:px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm sm:text-base tap-target"
|
||||
>
|
||||
<option value="createdAt">Created Date</option>
|
||||
<option value="dueDate">Due Date</option>
|
||||
@@ -131,8 +131,9 @@ export default function FilterPanel({
|
||||
</select>
|
||||
<button
|
||||
onClick={handleSortOrderToggle}
|
||||
className="px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
className="px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 active:bg-gray-100 transition-colors tap-target text-lg font-medium"
|
||||
title={filters.sortOrder === 'asc' ? 'Ascending' : 'Descending'}
|
||||
aria-label={filters.sortOrder === 'asc' ? 'Sort ascending' : 'Sort descending'}
|
||||
>
|
||||
{filters.sortOrder === 'asc' ? '↑' : '↓'}
|
||||
</button>
|
||||
@@ -144,43 +145,43 @@ export default function FilterPanel({
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Priority
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<button
|
||||
onClick={() => handlePriorityChange(null)}
|
||||
className={`flex-1 px-3 py-2 rounded-lg border transition-colors ${
|
||||
className={`px-2 sm:px-3 py-2.5 sm:py-3 rounded-lg border transition-colors tap-target text-xs sm:text-sm font-medium ${
|
||||
filters.priority === null
|
||||
? 'bg-blue-50 border-blue-500 text-blue-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handlePriorityChange('low')}
|
||||
className={`flex-1 px-3 py-2 rounded-lg border transition-colors ${
|
||||
className={`px-2 sm:px-3 py-2.5 sm:py-3 rounded-lg border transition-colors tap-target text-xs sm:text-sm font-medium ${
|
||||
filters.priority === 'low'
|
||||
? 'bg-green-50 border-green-500 text-green-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
Low
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handlePriorityChange('medium')}
|
||||
className={`flex-1 px-3 py-2 rounded-lg border transition-colors ${
|
||||
className={`px-2 sm:px-3 py-2.5 sm:py-3 rounded-lg border transition-colors tap-target text-xs sm:text-sm font-medium ${
|
||||
filters.priority === 'medium'
|
||||
? 'bg-yellow-50 border-yellow-500 text-yellow-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
Medium
|
||||
Med
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handlePriorityChange('high')}
|
||||
className={`flex-1 px-3 py-2 rounded-lg border transition-colors ${
|
||||
className={`px-2 sm:px-3 py-2.5 sm:py-3 rounded-lg border transition-colors tap-target text-xs sm:text-sm font-medium ${
|
||||
filters.priority === 'high'
|
||||
? 'bg-red-50 border-red-500 text-red-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
High
|
||||
@@ -193,36 +194,36 @@ export default function FilterPanel({
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Status
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<button
|
||||
onClick={() => handleCompletedChange(null)}
|
||||
className={`flex-1 px-3 py-2 rounded-lg border transition-colors ${
|
||||
className={`px-3 py-2.5 sm:py-3 rounded-lg border transition-colors tap-target text-xs sm:text-sm font-medium ${
|
||||
filters.completed === null
|
||||
? 'bg-blue-50 border-blue-500 text-blue-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCompletedChange(false)}
|
||||
className={`flex-1 px-3 py-2 rounded-lg border transition-colors ${
|
||||
className={`px-3 py-2.5 sm:py-3 rounded-lg border transition-colors tap-target text-xs sm:text-sm font-medium ${
|
||||
filters.completed === false
|
||||
? 'bg-blue-50 border-blue-500 text-blue-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
Incomplete
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleCompletedChange(true)}
|
||||
className={`flex-1 px-3 py-2 rounded-lg border transition-colors ${
|
||||
className={`px-3 py-2.5 sm:py-3 rounded-lg border transition-colors tap-target text-xs sm:text-sm font-medium ${
|
||||
filters.completed === true
|
||||
? 'bg-blue-50 border-blue-500 text-blue-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
Completed
|
||||
Complete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,10 +239,10 @@ export default function FilterPanel({
|
||||
<button
|
||||
key={tag}
|
||||
onClick={() => handleTagToggle(tag)}
|
||||
className={`px-3 py-1 rounded-full text-sm border transition-colors ${
|
||||
className={`px-3 py-2 rounded-full text-xs sm:text-sm border transition-colors tap-target-sm font-medium ${
|
||||
filters.tags.includes(tag)
|
||||
? 'bg-blue-50 border-blue-500 text-blue-700'
|
||||
: 'border-gray-300 hover:bg-gray-50'
|
||||
: 'border-gray-300 hover:bg-gray-50 active:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
{tag}
|
||||
@@ -256,23 +257,23 @@ export default function FilterPanel({
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Due Date Range
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs text-gray-500 mb-1">From</label>
|
||||
<label className="block text-xs text-gray-500 mb-1.5">From</label>
|
||||
<input
|
||||
type="date"
|
||||
value={filters.dueDateRange.start ? filters.dueDateRange.start.toISOString().split('T')[0] : ''}
|
||||
onChange={(e) => handleDueDateStartChange(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full px-3 sm:px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm sm:text-base tap-target"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-gray-500 mb-1">To</label>
|
||||
<label className="block text-xs text-gray-500 mb-1.5">To</label>
|
||||
<input
|
||||
type="date"
|
||||
value={filters.dueDateRange.end ? filters.dueDateRange.end.toISOString().split('T')[0] : ''}
|
||||
onChange={(e) => handleDueDateEndChange(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full px-3 sm:px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm sm:text-base tap-target"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -282,9 +283,9 @@ export default function FilterPanel({
|
||||
{hasActiveFilters && (
|
||||
<button
|
||||
onClick={handleClearFilters}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors"
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 text-gray-700 rounded-lg transition-colors tap-target text-sm sm:text-base font-medium"
|
||||
>
|
||||
<X size={16} />
|
||||
<X size={16} className="flex-shrink-0" />
|
||||
Clear All Filters
|
||||
</button>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user