fix(ux-audit): landing next/image + confirm AlertDialog + search-modal i18n
Landing page:
- <img> → next/image avec width/height (CLS fix)
- padding nav responsive px-4 sm:px-8
note-card:
- confirm() natif → AlertDialog (non-bloquant, stylable)
- showLeaveDialog state + dialog component
search-modal (20 chaînes FR → i18n):
- useLanguage ajouté
- 20 strings FR hardcoded → t('searchModal.*')
- Clés ajoutées dans en.json + fr.json
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useNotebooks } from '@/context/notebooks-context'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import type { Note } from '@/lib/types'
|
||||
|
||||
interface SearchMatch {
|
||||
@@ -58,6 +59,7 @@ function escapeRegExp(s: string) {
|
||||
export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
const router = useRouter()
|
||||
const { notebooks } = useNotebooks()
|
||||
const { t } = useLanguage()
|
||||
|
||||
const [query, setQuery] = useState('')
|
||||
const [useRegex, setUseRegex] = useState(false)
|
||||
@@ -413,7 +415,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
className="fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm flex items-center justify-center z-[200] p-4 sm:p-6 select-none"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Recherche"
|
||||
aria-label={t('searchModal.ariaLabel')}
|
||||
onClick={(e) => { if (e.target === e.currentTarget) onClose() }}
|
||||
>
|
||||
<motion.div
|
||||
@@ -432,13 +434,13 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
placeholder="Rechercher dans toutes vos notes…"
|
||||
placeholder={t('searchModal.placeholder')}
|
||||
className="w-full text-sm pl-10 pr-28 py-2.5 rounded-xl border border-border/70 dark:border-zinc-800 bg-white/85 dark:bg-[#1C1C1C] text-ink dark:text-dark-ink placeholder-concrete/50 outline-none focus:border-blueprint transition-colors"
|
||||
/>
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => setCaseSensitive(c => !c)}
|
||||
title="Respecter la casse"
|
||||
title={t('searchModal.caseSensitive')}
|
||||
className={`px-1.5 py-1 text-[9.5px] font-bold rounded-md transition-colors select-none ${
|
||||
caseSensitive ? 'text-blueprint bg-blueprint/8' : 'text-concrete hover:bg-black/5 dark:hover:bg-white/5'
|
||||
}`}
|
||||
@@ -447,7 +449,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setUseRegex(r => !r)}
|
||||
title="Mode regex"
|
||||
title={t('searchModal.regexMode')}
|
||||
className={`px-1.5 py-1 text-[9.5px] font-bold rounded-md transition-colors select-none ${
|
||||
useRegex ? 'text-blueprint bg-blueprint/8' : 'text-concrete hover:bg-black/5 dark:hover:bg-white/5'
|
||||
}`}
|
||||
@@ -463,7 +465,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
{/* Saved queries */}
|
||||
{savedQueries.length > 0 && (
|
||||
<div className="flex items-center gap-2 text-[10px] text-concrete">
|
||||
<span className="uppercase text-[9px] font-bold">Favoris :</span>
|
||||
<span className="uppercase text-[9px] font-bold">{t('searchModal.favorites')}</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{savedQueries.map(sq => (
|
||||
<button
|
||||
@@ -507,12 +509,12 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
</div>
|
||||
<span className="text-[11px] font-medium text-concrete">
|
||||
{isLoading
|
||||
? 'Recherche en cours…'
|
||||
? t('searchModal.searching')
|
||||
: filteredMatches.length > 0
|
||||
? `${filteredMatches.length} occurrence${filteredMatches.length > 1 ? 's' : ''} dans ${docMatchesCount} note${docMatchesCount > 1 ? 's' : ''}`
|
||||
: query.trim()
|
||||
? 'Aucun résultat'
|
||||
: 'Tapez pour rechercher'}
|
||||
? t('searchModal.noResults')
|
||||
: t('searchModal.typeToSearch')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -531,7 +533,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
onChange={e => setSearchInTrash(e.target.checked)}
|
||||
className="rounded border-border/60 text-blueprint focus:ring-blueprint w-3 h-3"
|
||||
/>
|
||||
<span>Corbeille incluse</span>
|
||||
<span>{t('searchModal.trashIncluded')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -547,13 +549,13 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<Sparkles size={12} className="text-brand-accent" />
|
||||
<span className="text-[9px] font-bold uppercase tracking-widest text-brand-accent">
|
||||
{overviewLoading ? 'Analyse...' : 'Réponse IA'}
|
||||
{overviewLoading ? t('searchModal.aiAnalysis') : t('searchModal.aiResponse')}
|
||||
</span>
|
||||
</div>
|
||||
{overviewLoading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 size={12} className="animate-spin text-brand-accent" />
|
||||
<span className="text-[11px] text-muted-foreground">Synthèse des résultats...</span>
|
||||
<span className="text-[11px] text-muted-foreground">t('searchModal.resultsSummary')</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-[12px] leading-relaxed text-foreground/80">{overview}</p>
|
||||
@@ -614,7 +616,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
<div className="h-full flex flex-col items-center justify-center text-center p-6 text-concrete pt-24 space-y-2">
|
||||
<Search size={20} className="opacity-30 animate-pulse" />
|
||||
<p className="text-[11px] font-medium italic opacity-70">
|
||||
{query.trim() ? 'Aucune note ne correspond.' : 'Tapez pour obtenir des résultats instantanés.'}
|
||||
{query.trim() ? t('searchModal.noMatch') : t('searchModal.typeForResults')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -644,7 +646,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
{activeMatch.noteTitle}
|
||||
</h4>
|
||||
<p className="text-[8px] uppercase tracking-wider text-concrete font-bold mt-0.5">
|
||||
APERÇU CONTEXTUEL
|
||||
{t('searchModal.documentPreview')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -662,7 +664,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
className="px-5 py-2.5 bg-ink text-white dark:bg-white dark:text-black hover:opacity-90 text-xs font-semibold rounded-xl flex items-center gap-2 transition-all shadow-sm"
|
||||
>
|
||||
<CornerDownRight size={13} />
|
||||
<span>Ouvrir dans l'éditeur</span>
|
||||
<span>{t('searchModal.openInEditor')}</span>
|
||||
</button>
|
||||
<span className="text-[10px] text-concrete font-bold font-mono bg-paper dark:bg-white/5 border border-border/30 px-2 py-1 rounded">
|
||||
ID: {activeMatch.noteId.slice(0, 8)}…
|
||||
@@ -673,7 +675,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-center p-6 text-concrete space-y-3">
|
||||
<HelpCircle size={24} className="opacity-25" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-[11.5px] font-bold">Aperçu du document</p>
|
||||
<p className="text-[11.5px] font-bold">{t('searchModal.documentPreview')}</p>
|
||||
<p className="text-[10px] italic opacity-60">
|
||||
Sélectionnez un résultat pour explorer son contenu.
|
||||
</p>
|
||||
@@ -688,20 +690,20 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
<div className="flex items-center gap-5 text-[9.5px] font-bold text-concrete/70">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<kbd className="bg-slate-200 dark:bg-zinc-800 px-1 py-0.5 rounded text-ink dark:text-white text-[9px]">↑↓</kbd>
|
||||
naviguer
|
||||
{t('searchModal.hintNavigate')}
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<kbd className="bg-slate-200 dark:bg-zinc-800 px-1 py-0.5 rounded text-ink dark:text-white text-[9px]">Entrée</kbd>
|
||||
ouvrir
|
||||
{t('searchModal.hintOpen')}
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<kbd className="bg-slate-200 dark:bg-zinc-800 px-1 py-0.5 rounded text-ink dark:text-white text-[9px]">Échap</kbd>
|
||||
fermer
|
||||
{t('searchModal.hintClose')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-[9px] font-bold uppercase tracking-wider text-concrete/50">
|
||||
<Command size={10} />
|
||||
<span>Memento Search</span>
|
||||
<span>{t('searchModal.title')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user