fix(ui): sélection groupée dans la boîte de réception
Permet de cocher plusieurs notes, les déplacer vers un carnet ou les envoyer à la corbeille, et rend la carte mentale plus lisible. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -53,6 +53,7 @@ import { useHydrated } from '@/lib/use-hydrated'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import { fr } from 'date-fns/locale/fr'
|
||||
import { enUS } from 'date-fns/locale/en-US'
|
||||
import { NoteSelectCheckbox } from '@/components/note-select-checkbox'
|
||||
|
||||
export type NotesLayoutMode = 'grid' | 'list' | 'table' | 'kanban' | 'gallery'
|
||||
export type NotesClassicLayoutMode = 'grid' | 'list' | 'table'
|
||||
@@ -253,6 +254,12 @@ type NotesListViewsProps = {
|
||||
onOpenHistory?: (note: Note) => void
|
||||
notebookName?: string
|
||||
onGridReorder?: (orderedIds: string[]) => void | Promise<void>
|
||||
selection?: {
|
||||
selectedIds: ReadonlySet<string>
|
||||
onToggle: (id: string) => void
|
||||
onToggleAll?: () => void
|
||||
allSelected?: boolean
|
||||
}
|
||||
} & Partial<NoteCollectionActions>
|
||||
|
||||
export function NotesListViews({
|
||||
@@ -269,6 +276,7 @@ export function NotesListViews({
|
||||
onNotePatch,
|
||||
onNoteIllustrationGenerated,
|
||||
onGridReorder,
|
||||
selection,
|
||||
}: NotesListViewsProps) {
|
||||
const { t, language } = useLanguage()
|
||||
const { data: session } = useSession()
|
||||
@@ -352,6 +360,7 @@ export function NotesListViews({
|
||||
onOpenHistory={onOpenHistory}
|
||||
onGridReorder={onGridReorder}
|
||||
pinnedLabel={t('notes.pinned')}
|
||||
selection={selection}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -363,6 +372,15 @@ export function NotesListViews({
|
||||
<table className="w-full text-left border-collapse min-w-[720px]">
|
||||
<thead>
|
||||
<tr className="border-b border-border/30">
|
||||
{selection && (
|
||||
<th className="w-10 px-3 py-3">
|
||||
<NoteSelectCheckbox
|
||||
checked={!!selection.allSelected}
|
||||
onToggle={() => selection.onToggleAll?.()}
|
||||
label={selection.allSelected ? t('notes.deselectAll') : t('notes.selectAll')}
|
||||
/>
|
||||
</th>
|
||||
)}
|
||||
<th
|
||||
className="w-[32%] px-4 py-3 text-[10px] uppercase tracking-widest font-black text-muted-foreground cursor-pointer hover:text-foreground"
|
||||
onClick={() => handleSort('title')}
|
||||
@@ -407,6 +425,15 @@ export function NotesListViews({
|
||||
role="button"
|
||||
className="h-11 hover:bg-foreground/[0.02] cursor-pointer transition-colors group focus-visible:bg-foreground/[0.04]"
|
||||
>
|
||||
{selection && (
|
||||
<td className="px-3 py-2 w-10">
|
||||
<NoteSelectCheckbox
|
||||
checked={selection.selectedIds.has(note.id)}
|
||||
onToggle={() => selection.onToggle(note.id)}
|
||||
label={t('notes.selectNote')}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
<td className="px-4 py-2 font-memento-serif text-[13px] font-medium truncate max-w-[280px]">
|
||||
<span className="inline-flex items-center gap-2 truncate group-hover:text-brand-accent transition-colors">
|
||||
{note.isPinned && <Pin size={11} className="text-amber-500 fill-amber-500 shrink-0" />}
|
||||
@@ -464,6 +491,7 @@ export function NotesListViews({
|
||||
onMoveToNotebook={onMoveToNotebook}
|
||||
onNotePatch={onNotePatch}
|
||||
onNoteIllustrationGenerated={onNoteIllustrationGenerated}
|
||||
selection={selection}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -479,6 +507,7 @@ export function NotesListViews({
|
||||
onMoveToNotebook={onMoveToNotebook}
|
||||
onNotePatch={onNotePatch}
|
||||
onNoteIllustrationGenerated={onNoteIllustrationGenerated}
|
||||
selection={selection}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -497,6 +526,13 @@ function formatGridCardDate(date: Date | string, language: string): string {
|
||||
return `${month.toUpperCase()} ${day}, ${year}`
|
||||
}
|
||||
|
||||
type NotesSelection = {
|
||||
selectedIds: ReadonlySet<string>
|
||||
onToggle: (id: string) => void
|
||||
onToggleAll?: () => void
|
||||
allSelected?: boolean
|
||||
}
|
||||
|
||||
type GridCardSharedProps = {
|
||||
note: Note
|
||||
index: number
|
||||
@@ -512,6 +548,7 @@ type GridCardSharedProps = {
|
||||
onNoteIllustrationDeleted?: (noteId: string) => void | Promise<void>
|
||||
onOpenHistory?: (note: Note) => void
|
||||
isOverlay?: boolean
|
||||
selection?: NotesSelection
|
||||
}
|
||||
|
||||
function NotesMasonryGrid({
|
||||
@@ -624,6 +661,7 @@ function NotesGridSection({
|
||||
onDeleteNote,
|
||||
onMoveToNotebook,
|
||||
onNoteIllustrationGenerated,
|
||||
selection,
|
||||
className,
|
||||
}: Omit<GridCardSharedProps, 'note' | 'index' | 'isOverlay'> & {
|
||||
notes: Note[]
|
||||
@@ -650,6 +688,7 @@ function NotesGridSection({
|
||||
onDeleteNote={onDeleteNote}
|
||||
onMoveToNotebook={onMoveToNotebook}
|
||||
onNoteIllustrationGenerated={onNoteIllustrationGenerated}
|
||||
selection={selection}
|
||||
/>
|
||||
) : (
|
||||
<GridCard
|
||||
@@ -665,6 +704,7 @@ function NotesGridSection({
|
||||
onDeleteNote={onDeleteNote}
|
||||
onMoveToNotebook={onMoveToNotebook}
|
||||
onNoteIllustrationGenerated={onNoteIllustrationGenerated}
|
||||
selection={selection}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
@@ -721,6 +761,7 @@ const GridCard = memo(function GridCard({
|
||||
onNoteIllustrationDeleted,
|
||||
onOpenHistory,
|
||||
isOverlay = false,
|
||||
selection,
|
||||
}: GridCardSharedProps) {
|
||||
const router = useRouter()
|
||||
const { t, language } = useLanguage()
|
||||
@@ -760,8 +801,21 @@ const GridCard = memo(function GridCard({
|
||||
onNoteIllustrationGenerated={onNoteIllustrationGenerated}
|
||||
onNoteIllustrationDeleted={onNoteIllustrationDeleted}
|
||||
/>
|
||||
{selection && !isOverlay && (
|
||||
<div className="absolute top-2 start-2 z-20">
|
||||
<NoteSelectCheckbox
|
||||
checked={selection.selectedIds.has(note.id)}
|
||||
onToggle={() => selection.onToggle(note.id)}
|
||||
label={t('notes.selectNote')}
|
||||
className="shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{note.isPinned && (
|
||||
<div className="absolute top-3 start-3 bg-background/90 backdrop-blur-sm p-1.5 rounded-full shadow-sm border border-border/40 text-amber-500">
|
||||
<div className={cn(
|
||||
'absolute top-3 bg-background/90 backdrop-blur-sm p-1.5 rounded-full shadow-sm border border-border/40 text-amber-500',
|
||||
selection ? 'start-10' : 'start-3',
|
||||
)}>
|
||||
<Pin size={11} className="fill-amber-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user