cognitive-load: add tooltips to advanced features
- Added optional tooltip prop to AdvancedFilters component - Added tooltips to LibraryPage view mode toggles (Grid/List) - Added tooltip to LibraryPage sort button - Added context-aware tooltip to LibraryPage bulk mode button - Tooltips explain functionality and improve discoverability - Action 10.4.1.2 complete
This commit is contained in:
parent
e2b03341b2
commit
7ab4e03ded
3 changed files with 102 additions and 63 deletions
|
|
@ -3586,12 +3586,18 @@ Critical path dependencies:
|
|||
- **Result**: Tooltip component is ready to use, no dependency needed
|
||||
- **Rollback**: N/A (component already exists)
|
||||
|
||||
- [ ] **Action 10.4.1.2**: Add tooltips to advanced features
|
||||
- [x] **Action 10.4.1.2**: Add tooltips to advanced features
|
||||
- **Scope**: Advanced UI elements - Add tooltips explaining usage
|
||||
- **Dependencies**: Action 10.4.1.1 complete
|
||||
- **Dependencies**: Action 10.4.1.1 complete ✅
|
||||
- **Risk**: LOW 🔒
|
||||
- **Validation**: Tooltips appear on hover
|
||||
- **Rollback**: Remove tooltips
|
||||
- **Validation**: ✅ Tooltips added to advanced features:
|
||||
- **AdvancedFilters component**: Added optional `tooltip` prop to explain what advanced filters are
|
||||
- **LibraryPage view mode toggles**: Added tooltips to Grid and List view buttons explaining each view type
|
||||
- **LibraryPage sort button**: Added tooltip explaining sorting functionality
|
||||
- **LibraryPage bulk mode button**: Added tooltip explaining bulk selection mode (context-aware: different text when active vs inactive)
|
||||
- **Tooltips use**: Tooltip component from `@/components/ui/tooltip` with hover trigger
|
||||
- **Result**: Advanced features now have helpful tooltips that appear on hover, improving discoverability and reducing cognitive load
|
||||
- **Rollback**: Remove tooltip props and Tooltip wrappers
|
||||
|
||||
- [ ] **Action 10.4.1.3**: Create onboarding flow for new users (optional)
|
||||
- **Scope**: `apps/web/src/components/Onboarding.tsx` (create) - Guide new users through features
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
|||
import { ChevronDown, ChevronUp, Filter } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Collapsible } from './ui/collapsible';
|
||||
import { Tooltip } from './ui/tooltip';
|
||||
|
||||
export interface AdvancedFiltersProps {
|
||||
/**
|
||||
|
|
@ -46,6 +47,12 @@ export interface AdvancedFiltersProps {
|
|||
* @default true
|
||||
*/
|
||||
showIcon?: boolean;
|
||||
|
||||
/**
|
||||
* Tooltip text to explain what advanced filters are
|
||||
* If provided, wraps the trigger in a Tooltip component
|
||||
*/
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -87,6 +94,7 @@ export function AdvancedFilters({
|
|||
className,
|
||||
contentClassName,
|
||||
showIcon = true,
|
||||
tooltip,
|
||||
}: AdvancedFiltersProps) {
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
||||
|
||||
|
|
@ -103,16 +111,26 @@ export function AdvancedFilters({
|
|||
|
||||
const ChevronIcon = isOpen ? ChevronUp : ChevronDown;
|
||||
|
||||
const triggerContent = (
|
||||
<div className="flex items-center gap-2">
|
||||
{showIcon && <Filter className="w-4 h-4" />}
|
||||
<span>{label}</span>
|
||||
<ChevronIcon className="w-4 h-4 ml-auto" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const collapsibleTrigger = tooltip ? (
|
||||
<Tooltip content={tooltip} position="top">
|
||||
{triggerContent}
|
||||
</Tooltip>
|
||||
) : (
|
||||
triggerContent
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn('w-full', className)}>
|
||||
<Collapsible
|
||||
trigger={
|
||||
<div className="flex items-center gap-2">
|
||||
{showIcon && <Filter className="w-4 h-4" />}
|
||||
<span>{label}</span>
|
||||
<ChevronIcon className="w-4 h-4 ml-auto" />
|
||||
</div>
|
||||
}
|
||||
trigger={collapsibleTrigger}
|
||||
open={isOpen}
|
||||
onOpenChange={handleToggle}
|
||||
defaultOpen={defaultOpen}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import { ConfirmationDialog } from '@/components/ui/confirmation-dialog';
|
|||
import { LoadingState } from '@/components/ui/LoadingState';
|
||||
import { Sidebar } from '@/components/ui/Sidebar';
|
||||
import { BulkModeBanner } from '@/components/BulkModeBanner';
|
||||
import { Tooltip } from '@/components/ui/tooltip';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { parseApiError } from '@/utils/apiErrorHandler';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
|
@ -370,49 +371,61 @@ export default function LibraryPagePremium() {
|
|||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant={isBulkMode ? 'default' : 'outline'}
|
||||
onClick={() => {
|
||||
setIsBulkMode(!isBulkMode);
|
||||
setSelectedTracks(new Set());
|
||||
}}
|
||||
size="sm"
|
||||
<Tooltip
|
||||
content={
|
||||
isBulkMode
|
||||
? 'Annuler la sélection multiple'
|
||||
: 'Activer le mode sélection multiple pour modifier plusieurs pistes à la fois'
|
||||
}
|
||||
>
|
||||
{isBulkMode ? (
|
||||
<>
|
||||
<X className="mr-2 h-4 w-4" />
|
||||
Annuler
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckSquare className="mr-2 h-4 w-4" />
|
||||
Sélection
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant={isBulkMode ? 'default' : 'outline'}
|
||||
onClick={() => {
|
||||
setIsBulkMode(!isBulkMode);
|
||||
setSelectedTracks(new Set());
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
{isBulkMode ? (
|
||||
<>
|
||||
<X className="mr-2 h-4 w-4" />
|
||||
Annuler
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckSquare className="mr-2 h-4 w-4" />
|
||||
Sélection
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<div className="flex items-center border border-white/10 rounded-lg overflow-hidden">
|
||||
<button
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={cn(
|
||||
'p-2 transition-colors cursor-pointer hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-cyan focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void',
|
||||
viewMode === 'grid'
|
||||
? 'bg-kodo-cyan/20 text-kodo-cyan'
|
||||
: 'text-kodo-secondary hover:text-white',
|
||||
)}
|
||||
>
|
||||
<Grid3x3 className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={cn(
|
||||
'p-2 transition-colors border-l border-white/10 cursor-pointer hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-cyan focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void',
|
||||
viewMode === 'list'
|
||||
? 'bg-kodo-cyan/20 text-kodo-cyan'
|
||||
: 'text-kodo-secondary hover:text-white',
|
||||
)}
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
</button>
|
||||
<Tooltip content="Vue en grille - Affiche les pistes sous forme de cartes visuelles">
|
||||
<button
|
||||
onClick={() => setViewMode('grid')}
|
||||
className={cn(
|
||||
'p-2 transition-colors cursor-pointer hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-cyan focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void',
|
||||
viewMode === 'grid'
|
||||
? 'bg-kodo-cyan/20 text-kodo-cyan'
|
||||
: 'text-kodo-secondary hover:text-white',
|
||||
)}
|
||||
>
|
||||
<Grid3x3 className="w-4 h-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip content="Vue en liste - Affiche les pistes avec plus de détails et métadonnées">
|
||||
<button
|
||||
onClick={() => setViewMode('list')}
|
||||
className={cn(
|
||||
'p-2 transition-colors border-l border-white/10 cursor-pointer hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-cyan focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void',
|
||||
viewMode === 'list'
|
||||
? 'bg-kodo-cyan/20 text-kodo-cyan'
|
||||
: 'text-kodo-secondary hover:text-white',
|
||||
)}
|
||||
>
|
||||
<List className="w-4 h-4" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button onClick={handleOpenUpload} size="sm">
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
|
|
@ -485,17 +498,19 @@ export default function LibraryPagePremium() {
|
|||
</label>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="w-full justify-between">
|
||||
<span className="flex items-center gap-2">
|
||||
<ArrowUpDown className="h-4 w-4" />
|
||||
{sortBy === 'created_at'
|
||||
? 'Date'
|
||||
: sortBy === 'title'
|
||||
? 'Titre'
|
||||
: 'Popularité'}
|
||||
{sortOrder === 'asc' ? ' ↑' : ' ↓'}
|
||||
</span>
|
||||
</Button>
|
||||
<Tooltip content="Trier les pistes par date, titre ou popularité">
|
||||
<Button variant="outline" size="sm" className="w-full justify-between">
|
||||
<span className="flex items-center gap-2">
|
||||
<ArrowUpDown className="h-4 w-4" />
|
||||
{sortBy === 'created_at'
|
||||
? 'Date'
|
||||
: sortBy === 'title'
|
||||
? 'Titre'
|
||||
: 'Popularité'}
|
||||
{sortOrder === 'asc' ? ' ↑' : ' ↓'}
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Trier par</DropdownMenuLabel>
|
||||
|
|
|
|||
Loading…
Reference in a new issue