Files
Momento/memento-note/components/note-actions.tsx
Antigravity bb75b2e763
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 4s
docs: add comprehensive Stripe billing guide
Covers architecture, configuration steps, user flows, API routes,
webhooks, pricing, testing with Stripe CLI, production checklist,
and troubleshooting.
2026-05-16 21:10:26 +00:00

293 lines
8.7 KiB
TypeScript

import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Archive,
ArchiveRestore,
Bell,
MoreVertical,
Palette,
Pin,
Users,
Maximize2,
FileText,
Trash2,
RotateCcw,
History,
AlignLeft,
FileCode2,
PenLine,
ListChecks,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { NOTE_COLORS } from "@/lib/types"
import { useLanguage } from "@/lib/i18n"
import { ReminderDialog } from "@/components/reminder-dialog"
import { useState } from "react"
interface NoteActionsProps {
isPinned: boolean
isArchived: boolean
currentColor: string
currentSize?: 'small' | 'medium' | 'large'
onTogglePin: () => void
onToggleArchive: () => void
onColorChange: (color: string) => void
onSizeChange?: (size: 'small' | 'medium' | 'large') => void
onDelete: () => void
onShareCollaborators?: () => void
isMarkdown?: boolean
noteType?: string
onToggleMarkdown?: () => void
isTrashView?: boolean
onRestore?: () => void
onPermanentDelete?: () => void
onOpenHistory?: () => void
historyEnabled?: boolean
noteId?: string
currentReminder?: Date | null
onUpdateReminder?: (noteId: string, reminder: Date | null) => void
className?: string
}
export function NoteActions({
isPinned,
isArchived,
currentColor,
currentSize = 'small',
onTogglePin,
onToggleArchive,
onColorChange,
onSizeChange,
onDelete,
onShareCollaborators,
isMarkdown = false,
noteType = 'text',
onToggleMarkdown,
isTrashView,
onRestore,
onPermanentDelete,
onOpenHistory,
historyEnabled = false,
noteId,
currentReminder,
onUpdateReminder,
className
}: NoteActionsProps) {
const { t } = useLanguage()
const [showReminder, setShowReminder] = useState(false)
// Trash view: show only Restore and Permanent Delete
if (isTrashView) {
return (
<div
className={cn("flex items-center justify-end gap-1", className)}
onClick={(e) => e.stopPropagation()}
>
{/* Restore Button */}
<Button
variant="ghost"
size="sm"
className="h-8 gap-1 px-2 text-xs"
onClick={onRestore}
title={t('trash.restore')}
>
<RotateCcw className="h-4 w-4" />
<span className="hidden sm:inline">{t('trash.restore')}</span>
</Button>
{/* Permanent Delete Button */}
<Button
variant="ghost"
size="sm"
className="h-8 gap-1 px-2 text-xs text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300"
onClick={onPermanentDelete}
title={t('trash.permanentDelete')}
>
<Trash2 className="h-4 w-4" />
<span className="hidden sm:inline">{t('trash.permanentDelete')}</span>
</Button>
</div>
)
}
return (
<div
className={cn("flex items-center justify-end gap-1", className)}
onClick={(e) => e.stopPropagation()}
>
{/* Reminder */}
{noteId && onUpdateReminder && (
<>
<Button
variant="ghost"
size="sm"
className={cn("h-8 w-8 p-0", currentReminder && "text-primary")}
title={t('reminder.setReminder')}
onClick={() => setShowReminder(true)}
>
<Bell className="h-4 w-4" />
</Button>
<div onClick={(e) => e.stopPropagation()}>
<ReminderDialog
open={showReminder}
onOpenChange={setShowReminder}
currentReminder={currentReminder || null}
onSave={(date) => {
onUpdateReminder(noteId, date)
setShowReminder(false)
}}
onRemove={() => {
onUpdateReminder(noteId, null)
setShowReminder(false)
}}
/>
</div>
</>
)}
{/* Color Palette */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0" title={t('notes.changeColor')}>
<Palette className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<div className="grid grid-cols-5 gap-2 p-2">
{Object.entries(NOTE_COLORS).map(([colorName, classes]) => (
<button
key={colorName}
className={cn(
'h-8 w-8 rounded-full border-2 transition-transform hover:scale-110',
classes.bg,
currentColor === colorName ? 'border-gray-900 dark:border-gray-100' : 'border-gray-300 dark:border-gray-700'
)}
onClick={() => onColorChange(colorName)}
title={colorName}
/>
))}
</div>
</DropdownMenuContent>
</DropdownMenu>
{onToggleMarkdown && (() => {
const iconMap: Record<string, React.ElementType> = { text: AlignLeft, markdown: FileCode2, richtext: PenLine, checklist: ListChecks }
const TypeIcon = iconMap[noteType] || FileText
return (
<Button
variant="ghost"
size="sm"
className={cn("h-8 gap-1 px-2 text-xs", noteType !== 'text' && "text-primary bg-primary/10")}
title={noteType === 'markdown' ? 'Markdown' : noteType === 'richtext' ? 'Rich Text' : noteType === 'checklist' ? 'Checklist' : 'Text'}
onClick={onToggleMarkdown}
>
<TypeIcon className="h-4 w-4" />
<span className="hidden sm:inline">{noteType === 'markdown' ? 'MD' : noteType === 'richtext' ? 'RT' : noteType === 'checklist' ? 'CL' : 'TXT'}</span>
</Button>
)
})()}
{/* More Options */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0" aria-label={t('notes.moreOptions')}>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{/* Pin/Unpin Option */}
<DropdownMenuItem onClick={onTogglePin}>
{isPinned ? (
<>
<Pin className="h-4 w-4 mr-2" />
{t('notes.unpin')}
</>
) : (
<>
<Pin className="h-4 w-4 mr-2" />
{t('notes.pin')}
</>
)}
</DropdownMenuItem>
<DropdownMenuItem onClick={onToggleArchive}>
{isArchived ? (
<>
<ArchiveRestore className="h-4 w-4 mr-2" />
{t('notes.unarchive')}
</>
) : (
<>
<Archive className="h-4 w-4 mr-2" />
{t('notes.archive')}
</>
)}
</DropdownMenuItem>
{onOpenHistory && (
<DropdownMenuItem onClick={onOpenHistory}>
<History className="h-4 w-4 mr-2" />
{historyEnabled
? t('notes.history')
: (t('notes.enableHistory') || "Activer l'historique")}
</DropdownMenuItem>
)}
{/* Size Selector */}
{onSizeChange && (
<>
<DropdownMenuSeparator />
<div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground">
{t('notes.size')}
</div>
{(['small', 'medium', 'large'] as const).map((size) => (
<DropdownMenuItem
key={size}
onSelect={(e) => {
onSizeChange?.(size);
}}
className={cn(
"capitalize",
currentSize === size && "bg-accent"
)}
>
<Maximize2 className="h-4 w-4 mr-2" />
{t(`notes.${size}` as const)}
</DropdownMenuItem>
))}
</>
)}
{/* Collaborators */}
{onShareCollaborators && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation()
onShareCollaborators()
}}
>
<Users className="h-4 w-4 mr-2" />
{t('notes.shareWithCollaborators')}
</DropdownMenuItem>
</>
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onDelete} className="text-red-600 dark:text-red-400">
<Trash2 className="h-4 w-4 mr-2" />
{t('notes.delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}