cleanup: remove unused reference folders and untracked scratch directories
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m1s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m1s
This commit is contained in:
@@ -1,357 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Sparkles,
|
||||
ChevronRight,
|
||||
MessageSquare,
|
||||
FileCode,
|
||||
Globe,
|
||||
Send,
|
||||
Scissors,
|
||||
Zap,
|
||||
Languages,
|
||||
Layout,
|
||||
ArrowRightLeft,
|
||||
BookOpen,
|
||||
History
|
||||
} from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { AITab, AITone, Note } from '../types';
|
||||
|
||||
interface AISidebarProps {
|
||||
isOpen: boolean;
|
||||
setIsOpen: (open: boolean) => void;
|
||||
activeNote: Note | undefined;
|
||||
aiTab: AITab;
|
||||
setAiTab: (tab: AITab) => void;
|
||||
selectedTone: AITone;
|
||||
setSelectedTone: (tone: AITone) => void;
|
||||
}
|
||||
|
||||
export const AISidebar: React.FC<AISidebarProps> = ({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
activeNote,
|
||||
aiTab,
|
||||
setAiTab,
|
||||
selectedTone,
|
||||
setSelectedTone
|
||||
}) => {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.aside
|
||||
initial={{ x: 400, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: 400, opacity: 0 }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||
className="w-[400px] border-l border-border bg-white shadow-2xl flex flex-col z-50 shrink-0 relative"
|
||||
>
|
||||
<div className="p-6 border-b border-border space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="flex items-center gap-2 font-serif text-xl font-medium text-ink">
|
||||
<Sparkles size={18} className="text-ochre" />
|
||||
IA Assistant
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="p-1 hover:bg-slate-100 rounded-full transition-colors text-muted-ink"
|
||||
>
|
||||
<ChevronRight size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-ink uppercase tracking-wider font-medium opacity-60 truncate">
|
||||
"{activeNote?.title}"
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex border-b border-border px-2">
|
||||
{(['discussion', 'actions', 'resources'] as AITab[]).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setAiTab(tab)}
|
||||
className={`flex-1 py-3 text-[10px] uppercase tracking-[0.2em] font-bold transition-all relative
|
||||
${aiTab === tab ? 'text-manganese' : 'text-muted-ink hover:text-ink/60'}`}
|
||||
>
|
||||
{tab}
|
||||
{aiTab === tab && (
|
||||
<motion.div
|
||||
layoutId="activeTab"
|
||||
className="absolute bottom-0 left-0 right-0 h-0.5 bg-ochre"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-6 custom-scrollbar">
|
||||
<AnimatePresence mode="wait">
|
||||
{aiTab === 'discussion' && (
|
||||
<motion.div
|
||||
key="discussion"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="h-64 flex flex-col items-center justify-center text-center space-y-4 text-muted-ink/40">
|
||||
<div className="w-16 h-16 rounded-full border border-dashed border-muted-ink/20 flex items-center justify-center">
|
||||
<MessageSquare size={24} />
|
||||
</div>
|
||||
<p className="text-xs font-serif italic leading-relaxed px-8">Posez une question à l'Assistant pour commencer.</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Contexte</label>
|
||||
<div className="w-full p-3 bg-glass border border-border rounded-lg text-xs flex items-center justify-between cursor-pointer hover:bg-white/40 transition-colors backdrop-blur-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileCode size={14} className="text-muted-ink" />
|
||||
<span>Cette note</span>
|
||||
</div>
|
||||
<ChevronRight size={14} className="rotate-90 text-muted-ink" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Ton d'écriture</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{(['Professional', 'Creative', 'Academic', 'Casual'] as AITone[]).map((tone) => (
|
||||
<button
|
||||
key={tone}
|
||||
onClick={() => setSelectedTone(tone)}
|
||||
className={`p-3 rounded-xl border text-[11px] font-medium transition-all
|
||||
${selectedTone === tone ? 'bg-manganese text-paper border-manganese shadow-lg shadow-manganese/10' : 'bg-glass border-border text-muted-ink hover:border-ink/20'}`}
|
||||
>
|
||||
{tone.toUpperCase().substring(0, 3)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{aiTab === 'actions' && (
|
||||
<motion.div
|
||||
key="actions"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<div className="h-px flex-1 bg-border/40" />
|
||||
<h4 className="text-[10px] uppercase tracking-[0.25em] font-bold text-muted-ink whitespace-nowrap">Transformations</h4>
|
||||
<div className="h-px flex-1 bg-border/40" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[
|
||||
{ icon: <Sparkles size={14} />, label: 'Clarifier', color: 'ochre' },
|
||||
{ icon: <Scissors size={14} />, label: 'Raccourcir', color: 'rust' },
|
||||
{ icon: <Zap size={14} />, label: 'Améliorer', color: 'sage' },
|
||||
{ icon: <Languages size={14} />, label: 'Traduire', color: 'slate' },
|
||||
].map((action, i) => (
|
||||
<button
|
||||
key={i}
|
||||
className="flex flex-col items-center gap-3 p-4 bg-glass border border-border rounded-xl transition-all group hover:border-ink/20"
|
||||
>
|
||||
<div className={`p-2 rounded-lg bg-slate-50 dark:bg-white/10 transition-colors group-hover:bg-manganese group-hover:text-paper shadow-sm text-ink/60`}>
|
||||
{action.icon}
|
||||
</div>
|
||||
<span className="text-[10px] font-bold text-ink/80 uppercase tracking-widest">{action.label}</span>
|
||||
</button>
|
||||
))}
|
||||
<button className="col-span-2 flex items-center justify-center gap-3 py-3 px-4 bg-glass border border-border rounded-xl text-[10px] font-bold text-ink/80 hover:bg-slate-50 dark:hover:bg-white/10 transition-colors hover:border-ink/20 uppercase tracking-widest">
|
||||
<FileCode size={14} className="text-muted-ink" />
|
||||
Convertir en Markdown
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="h-px flex-1 bg-border/40" />
|
||||
<h4 className="text-[10px] uppercase tracking-[0.25em] font-bold text-muted-ink whitespace-nowrap">Generation Tools</h4>
|
||||
<div className="h-px flex-1 bg-border/40" />
|
||||
</div>
|
||||
|
||||
<div className="group relative p-6 rounded-2xl bg-white border border-border hover:border-blueprint/30 transition-all duration-500 overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
|
||||
<Layout size={80} className="text-blueprint" />
|
||||
</div>
|
||||
|
||||
<div className="relative space-y-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-slate-50 rounded-lg text-blueprint">
|
||||
<Layout size={18} />
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<h5 className="text-sm font-bold text-ink leading-none">Présentation</h5>
|
||||
<p className="text-[10px] text-muted-ink uppercase tracking-tight">Convertir en slides interactives</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Thème</span>
|
||||
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-blueprint/10 transition-all cursor-pointer">
|
||||
<option>Architectural Mono</option>
|
||||
<option>Vibrant Tech</option>
|
||||
<option>Minimal Silk</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Style</span>
|
||||
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-blueprint/10 transition-all cursor-pointer">
|
||||
<option>Professional</option>
|
||||
<option>Creative</option>
|
||||
<option>Brutalist</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="w-full py-3.5 bg-blueprint text-paper rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-blueprint/20 uppercase tracking-[0.2em]">
|
||||
Générer
|
||||
<ArrowRightLeft size={14} className="opacity-60" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="group relative p-6 rounded-2xl bg-white border border-border hover:border-sage/30 transition-all duration-500 overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
|
||||
<BookOpen size={80} className="text-sage" />
|
||||
</div>
|
||||
|
||||
<div className="relative space-y-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-slate-50 rounded-lg text-sage">
|
||||
<BookOpen size={18} />
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<h5 className="text-sm font-bold text-ink leading-none">Diagramme</h5>
|
||||
<p className="text-[10px] text-muted-ink uppercase tracking-tight">Visualisation de structure</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Type</span>
|
||||
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-sage/10 transition-all cursor-pointer">
|
||||
<option>Logic Flow</option>
|
||||
<option>Mind Map</option>
|
||||
<option>Hierarchy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<span className="text-[9px] uppercase tracking-[0.2em] font-bold text-muted-ink/60 px-1">Style</span>
|
||||
<select className="w-full bg-glass dark:bg-black/20 border border-border rounded-lg px-2 py-2 text-xs outline-none focus:ring-1 ring-sage/10 transition-all cursor-pointer">
|
||||
<option>Draft</option>
|
||||
<option>Polished</option>
|
||||
<option>Handwritten</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="w-full py-3.5 bg-sage text-paper rounded-xl text-[11px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-sage/20 uppercase tracking-[0.2em]">
|
||||
Tracer
|
||||
<ArrowRightLeft size={14} className="opacity-60" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-2 opacity-20 py-4">
|
||||
<History size={16} />
|
||||
<span className="text-[10px] font-bold uppercase tracking-widest whitespace-nowrap">Auto-Save Enabled</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{aiTab === 'resources' && (
|
||||
<motion.div
|
||||
key="resources"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">URL (Optionnel)</label>
|
||||
<div className="relative">
|
||||
<input type="text" placeholder="https://..." className="w-full bg-glass border border-border rounded-lg pl-3 pr-10 py-3 text-xs outline-none focus:border-blueprint transition-colors" />
|
||||
<Globe size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-ink/40" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Texte de la ressource</label>
|
||||
<textarea
|
||||
rows={8}
|
||||
placeholder="Collez votre texte ici (markdown, HTML, texte brut...)"
|
||||
className="w-full bg-glass border border-border rounded-lg p-4 text-xs outline-none focus:border-blueprint transition-colors resize-none leading-relaxed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] uppercase tracking-[0.2em] font-bold text-muted-ink">Mode d'intégration</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{[
|
||||
{ id: 'replace', label: 'Remplacer', sub: 'Direct, sans IA' },
|
||||
{ id: 'append', label: 'Compléter', sub: 'Ajoute sans réécrire' },
|
||||
{ id: 'merge', label: 'Fusionner', sub: 'Réécrit et intègre' },
|
||||
].map((mode) => (
|
||||
<button key={mode.id} className={`flex flex-col items-center justify-center p-3 rounded-lg border transition-all text-center ${mode.id === 'append' ? 'bg-sage/10 border-sage/50 ring-1 ring-sage/10' : 'bg-white border-border hover:bg-slate-50'}`}>
|
||||
<span className={`text-[11px] font-bold ${mode.id === 'append' ? 'text-sage' : 'text-ink'}`}>{mode.label}</span>
|
||||
<span className="text-[8px] text-muted-ink opacity-60 leading-tight mt-1 font-medium">{mode.sub}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="w-full py-4 bg-blueprint text-white rounded-xl text-sm font-bold flex items-center justify-center gap-3 hover:opacity-90 transition-opacity shadow-lg shadow-blueprint/20">
|
||||
<Sparkles size={18} />
|
||||
Générer l'aperçu
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{aiTab === 'discussion' && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 20 }}
|
||||
className="p-6 bg-white border-t border-border"
|
||||
>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Posez une question sur cette note..."
|
||||
className="w-full bg-glass backdrop-blur-sm border border-border rounded-2xl p-4 pr-12 text-sm outline-none focus:border-blueprint transition-colors resize-none leading-relaxed font-light"
|
||||
/>
|
||||
<div className="absolute right-3 bottom-3 flex gap-2">
|
||||
<button className="p-2 text-muted-ink hover:text-ink rounded-lg transition-colors">
|
||||
<Globe size={16} />
|
||||
</button>
|
||||
<button className="p-2 bg-blueprint text-white rounded-lg transition-transform hover:scale-105 active:scale-95 shadow-lg shadow-blueprint/10">
|
||||
<Send size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[9px] text-muted-ink text-center mt-3 uppercase tracking-widest font-bold opacity-30 italic">Maj+Entrée = nouvelle ligne</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.aside>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
@@ -1,347 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Plus,
|
||||
ArrowLeft,
|
||||
Clock,
|
||||
Activity,
|
||||
Trash2,
|
||||
Edit3,
|
||||
Play,
|
||||
Eye,
|
||||
Microscope,
|
||||
Globe,
|
||||
Layers,
|
||||
Zap,
|
||||
BookOpen,
|
||||
Sparkles
|
||||
} from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
interface AgentsViewProps {
|
||||
selectedAgentId: string | null;
|
||||
setSelectedAgentId: (id: string | null) => void;
|
||||
}
|
||||
|
||||
export const AgentsView: React.FC<AgentsViewProps> = ({
|
||||
selectedAgentId,
|
||||
setSelectedAgentId
|
||||
}) => {
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-y-auto custom-scrollbar">
|
||||
{!selectedAgentId ? (
|
||||
<>
|
||||
<header className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-paper/80 backdrop-blur-md z-30">
|
||||
<div className="flex justify-between items-end">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-4xl font-serif font-medium tracking-tight text-ink">Mes Agents</h1>
|
||||
<p className="text-sm text-muted-ink font-light">Automatisez vos tâches de veille et de recherche.</p>
|
||||
</div>
|
||||
<button className="px-6 py-2.5 bg-ink text-paper text-sm font-medium rounded-xl hover:opacity-90 transition-all flex items-center gap-3 shadow-lg shadow-ink/10">
|
||||
<Plus size={18} />
|
||||
Nouvel Agent
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-8 border-b border-ink/5 pt-4">
|
||||
{['Tous', 'Veilleur', 'Chercheur', 'Surveillant', 'Personnalisé'].map((tag, i) => (
|
||||
<button key={i} className={`pb-4 text-xs font-bold uppercase tracking-widest transition-all relative ${i === 0 ? 'text-ink' : 'text-muted-ink hover:text-ink/60'}`}>
|
||||
{tag}
|
||||
{i === 0 && <motion.div layoutId="activeAgentTag" className="absolute bottom-0 left-0 right-0 h-0.5 bg-ink" />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="px-12 flex-1 pb-20 space-y-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{[
|
||||
{ id: 'a1', icon: <Eye size={20} className="text-amber-600" />, title: 'Surveillant de Notes', status: 'Réussi', type: 'SURVEILLANT', meta: 'Hebdomadaire • 6 exéc.', desc: 'Analyse les notes récentes d’un carnet et suggère des compléments, références et liens.' },
|
||||
{ id: 'a2', icon: <Microscope size={20} className="text-indigo-600" />, title: 'Chercheur de Sujet', status: 'Réussi', type: 'CHERCHEUR', meta: 'Hebdomadaire • 14 exéc.', desc: 'Recherche des informations approfondies sur les derniers modèles de Deepseek et voir l’avis des utilisateurs.' },
|
||||
{ id: 'a3', icon: <Globe size={20} className="text-emerald-600" />, title: 'Veille IA', status: 'Réussi', type: 'VEILLEUR', meta: 'Quotidien • 20 exéc.', desc: 'Scrape les flux RSS de 6 sites IA (The Verge, TechCrunch...) et génère un résumé.' },
|
||||
].map((agent, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick={() => setSelectedAgentId(agent.id)}
|
||||
className="bg-white dark:bg-white/5 border border-border rounded-2xl p-6 space-y-6 hover:border-ink/20 transition-all group cursor-pointer shadow-sm relative overflow-hidden"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-slate-50 dark:bg-white/10 rounded-xl group-hover:bg-ink group-hover:text-paper transition-all">
|
||||
{agent.icon}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-[13px] font-bold text-ink">{agent.title}</h4>
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-muted-ink opacity-60">{agent.type}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" className="sr-only peer" defaultChecked />
|
||||
<div className="w-8 h-4 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:bg-emerald-500"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-ink leading-relaxed line-clamp-3">
|
||||
{agent.desc}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between text-[10px] text-muted-ink font-medium">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="flex items-center gap-1"><Clock size={10} /> {agent.meta.split('•')[0]}</span>
|
||||
<span>{agent.meta.split('•')[1]}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-[10px] text-muted-ink font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="uppercase tracking-tight">Prochaine exécution</span>
|
||||
<span className="text-ink">Hebdomadaire</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="uppercase tracking-tight">Dernier statut</span>
|
||||
<span className="text-emerald-600 flex items-center gap-1"><Activity size={8} /> {agent.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2 border-t border-border pt-4">
|
||||
<button className="py-2 border border-border rounded-lg hover:bg-slate-50 dark:hover:bg-white/5 flex items-center justify-center transition-colors text-muted-ink hover:text-ink"><Edit3 size={14} /> <span className="ml-2 text-[10px] font-bold uppercase">Modifier</span></button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); }}
|
||||
className="py-2 border border-border rounded-lg hover:bg-slate-50 dark:hover:bg-white/5 flex items-center justify-center transition-colors text-muted-ink hover:text-ink"
|
||||
>
|
||||
<Play size={14} className="fill-current" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); }}
|
||||
className="py-2 border border-border rounded-lg hover:bg-rose-50 hover:text-rose-600 hover:border-rose-100 flex items-center justify-center transition-colors text-muted-ink"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<h5 className="text-[10px] font-bold uppercase tracking-[0.3em] text-muted-ink whitespace-nowrap">Modèles</h5>
|
||||
<div className="h-px w-full bg-border/40" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{[
|
||||
{ title: 'Veille IA', desc: 'Scrape les flux RSS de 6 sites IA et génère un résumé hebdomadaire.', icon: <Globe size={18} /> },
|
||||
{ title: 'Veille Tech', desc: 'Crée un résumé quotidien des news Hacker News et Product Hunt.', icon: <Zap size={18} /> },
|
||||
{ title: 'Veille Dev', desc: 'Surveille les repos GitHub pour détecter les nouvelles releases.', icon: <Layers size={18} /> },
|
||||
].map((model, i) => (
|
||||
<div key={i} className="bg-white/40 dark:bg-white/5 border border-dashed border-border rounded-2xl p-6 group cursor-pointer hover:bg-white dark:hover:bg-white/10 hover:border-ink/20 transition-all">
|
||||
<div className="w-8 h-8 rounded-lg bg-slate-50 dark:bg-white/10 flex items-center justify-center text-muted-ink group-hover:bg-ink group-hover:text-paper mb-4 transition-all">
|
||||
{model.icon}
|
||||
</div>
|
||||
<h4 className="text-[13px] font-bold text-ink mb-2">{model.title}</h4>
|
||||
<p className="text-xs text-muted-ink leading-relaxed mb-4">{model.desc}</p>
|
||||
<button className="text-[11px] font-bold uppercase tracking-widest text-ink hover:opacity-60 transition-opacity flex items-center gap-2">
|
||||
<Plus size={14} /> Installer
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="flex-1 flex flex-col"
|
||||
>
|
||||
<header className="px-12 py-10 border-b border-border bg-white dark:bg-paper backdrop-blur-md sticky top-0 z-30">
|
||||
<div className="flex items-center justify-between max-w-5xl mx-auto">
|
||||
<button
|
||||
onClick={() => setSelectedAgentId(null)}
|
||||
className="flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-muted-ink hover:text-ink transition-colors"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
Retour
|
||||
</button>
|
||||
<div className="flex items-center gap-4">
|
||||
<button className="px-5 py-2 text-xs font-bold uppercase tracking-widest border border-border rounded-xl hover:bg-slate-50 dark:hover:bg-white/5 transition-all">
|
||||
Logs
|
||||
</button>
|
||||
<button className="px-6 py-2 bg-ink text-paper text-xs font-bold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all shadow-lg shadow-ink/10">
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 px-12 py-16 max-w-5xl mx-auto w-full space-y-16">
|
||||
<section className="space-y-8">
|
||||
<div className="flex items-end justify-between">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-4 bg-ink text-paper rounded-2xl shadow-xl shadow-ink/10">
|
||||
{selectedAgentId === 'a1' ? <Eye size={32} /> : selectedAgentId === 'a2' ? <Microscope size={32} /> : <Globe size={32} />}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-3xl font-serif font-medium text-ink">
|
||||
{selectedAgentId === 'a1' ? 'Surveillant de Notes' : selectedAgentId === 'a2' ? 'Chercheur de Sujet' : 'Veille IA'}
|
||||
</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[10px] font-bold uppercase tracking-widest px-2 py-1 bg-slate-100 dark:bg-white/10 text-muted-ink rounded-md">ID: {selectedAgentId}</span>
|
||||
<span className="text-[10px] font-bold uppercase tracking-widest px-2 py-1 bg-emerald-50 dark:bg-emerald-500/10 text-emerald-600 rounded-md">Actif</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-8 text-[12px] uppercase tracking-[0.2em] font-bold text-muted-ink">
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<span className="opacity-40">Dernière exéc.</span>
|
||||
<span className="text-ink">Il y a 2h</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<span className="opacity-40">Succès</span>
|
||||
<span className="text-emerald-600 tracking-normal">98.4%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
||||
<div className="lg:col-span-2 space-y-12">
|
||||
<section className="space-y-6">
|
||||
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Configuration Logique</h3>
|
||||
<div className="bg-white dark:bg-white/5 border border-border rounded-3xl overflow-hidden shadow-sm">
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="space-y-4">
|
||||
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-ink">Source de données</label>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-4 rounded-2xl border-2 border-ink bg-slate-50 dark:bg-white/10 flex items-center justify-between group cursor-pointer transition-all">
|
||||
<div className="flex items-center gap-3">
|
||||
<BookOpen size={18} className="text-ink" />
|
||||
<span className="text-sm font-medium text-ink">Carnets Externes</span>
|
||||
</div>
|
||||
<div className="w-5 h-5 rounded-full border-4 border-ink flex items-center justify-center">
|
||||
<div className="w-2 h-2 bg-ink rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 rounded-2xl border border-border hover:border-ink/20 flex items-center justify-between group cursor-pointer transition-all">
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe size={18} className="text-muted-ink" />
|
||||
<span className="text-sm font-medium text-muted-ink">Web (Flux RSS/SEO)</span>
|
||||
</div>
|
||||
<div className="w-5 h-5 rounded-full border border-border" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-ink">Modèle d'Intelligence</label>
|
||||
<select className="w-full bg-slate-50 dark:bg-black/20 border border-border rounded-xl px-4 py-4 text-sm outline-none focus:ring-2 ring-ink/5 transition-all cursor-pointer font-medium text-ink">
|
||||
<option>Gemini 2.0 Flash (Optimisé)</option>
|
||||
<option>Gemini 1.5 Pro (Analytique)</option>
|
||||
<option>Claude 3.5 Sonnet (Via API)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-ink">Instruction Système (Prompt)</label>
|
||||
<textarea
|
||||
rows={6}
|
||||
className="w-full bg-slate-50 dark:bg-black/20 border border-border rounded-2xl p-6 text-sm outline-none focus:ring-2 ring-ink/5 transition-all font-light leading-relaxed resize-none text-ink"
|
||||
defaultValue="Tu es un assistant spécialisé dans l'analyse de notes architecturales. Ta mission est de scanner les nouveaux carnets, extraire les concepts de matérialité et suggérer des références historiques pertinentes."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-6">
|
||||
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Actions de Sortie</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-6 bg-white dark:bg-white/5 border border-border rounded-2xl space-y-4 hover:border-ink/20 transition-all cursor-pointer group">
|
||||
<div className="w-10 h-10 rounded-xl bg-slate-50 dark:bg-white/10 flex items-center justify-center text-muted-ink group-hover:bg-ink group-hover:text-paper transition-all">
|
||||
<Plus size={18} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-bold text-ink leading-tight">Nouvelle Note</h4>
|
||||
<p className="text-[10px] text-muted-ink font-medium uppercase tracking-tight">Créer un brouillon auto.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 bg-white dark:bg-white/5 border border-border rounded-2xl space-y-4 hover:border-ink/20 transition-all cursor-pointer group opacity-40 border-dashed">
|
||||
<div className="w-10 h-10 rounded-xl border border-dashed border-border flex items-center justify-center text-muted-ink group-hover:bg-ink group-hover:text-paper transition-all">
|
||||
<Sparkles size={18} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-bold text-ink leading-tight">Webhook API</h4>
|
||||
<p className="text-[10px] text-muted-ink font-medium uppercase tracking-tight">Ajouter une destination</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div className="space-y-10">
|
||||
<section className="space-y-6">
|
||||
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Planification</h3>
|
||||
<div className="bg-ink rounded-3xl p-8 space-y-8 text-paper shadow-2xl shadow-ink/20 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-4 opacity-10">
|
||||
<Clock size={100} />
|
||||
</div>
|
||||
<div className="relative space-y-6">
|
||||
<div className="space-y-2">
|
||||
<span className="text-[10px] uppercase tracking-widest font-bold opacity-60">Fréquence</span>
|
||||
<div className="text-2xl font-serif italic text-paper">Hebdomadaire</div>
|
||||
</div>
|
||||
<div className="h-px bg-white/10" />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="opacity-60">Lundi</span>
|
||||
<span className="font-bold">09:00</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="opacity-60">Jeudi</span>
|
||||
<span className="font-bold">14:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<button className="w-full py-3 bg-white text-ink text-xs font-bold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all">
|
||||
Modifier le planning
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-6">
|
||||
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-muted-ink">Notifications</h3>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ label: 'Rapport d\'exécution', enabled: true },
|
||||
{ label: 'Erreurs critiques', enabled: true },
|
||||
{ label: 'Modifications de notes', enabled: false },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="flex items-center justify-between p-4 bg-slate-50 dark:bg-white/10 rounded-2xl border border-border/50">
|
||||
<span className="text-xs font-medium text-ink">{item.label}</span>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" className="sr-only peer" defaultChecked={item.enabled} />
|
||||
<div className="w-8 h-4 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:bg-[#75B2D6]"></div>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="pt-6">
|
||||
<button className="w-full py-4 border border-rose-200 text-rose-600 bg-rose-50/50 dark:bg-rose-500/10 rounded-2xl text-xs font-bold uppercase tracking-widest hover:bg-rose-100 transition-colors flex items-center justify-center gap-3">
|
||||
<Trash2 size={16} />
|
||||
Supprimer l'agent
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,388 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Plus,
|
||||
Search,
|
||||
Share2,
|
||||
Pin,
|
||||
ChevronRight,
|
||||
ArrowLeft,
|
||||
MoreVertical,
|
||||
Sparkles,
|
||||
Tag as TagIcon,
|
||||
X,
|
||||
BookOpen
|
||||
} from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { Note, Carnet, Tag } from '../types';
|
||||
|
||||
interface NotebooksViewProps {
|
||||
activeNoteId: string | null;
|
||||
activeCarnet: Carnet | undefined;
|
||||
filteredNotes: Note[];
|
||||
activeNote: Note | undefined;
|
||||
setActiveNoteId: (id: string | null) => void;
|
||||
togglePin: (id: string) => void;
|
||||
setShowNewNoteModal: (show: boolean) => void;
|
||||
isAISidebarOpen: boolean;
|
||||
setIsAISidebarOpen: (open: boolean) => void;
|
||||
selectedTagIds: string[];
|
||||
setSelectedTagIds: (ids: string[]) => void;
|
||||
allNotes: Note[];
|
||||
activeCarnetId: string;
|
||||
setShowNewCarnetModal: (show: boolean, parentId?: string) => void;
|
||||
}
|
||||
|
||||
export const NotebooksView: React.FC<NotebooksViewProps> = ({
|
||||
activeNoteId,
|
||||
activeCarnet,
|
||||
filteredNotes,
|
||||
activeNote,
|
||||
setActiveNoteId,
|
||||
togglePin,
|
||||
setShowNewNoteModal,
|
||||
isAISidebarOpen,
|
||||
setIsAISidebarOpen,
|
||||
selectedTagIds,
|
||||
setSelectedTagIds,
|
||||
allNotes,
|
||||
activeCarnetId,
|
||||
setShowNewCarnetModal
|
||||
}) => {
|
||||
const [isTagsExpanded, setIsTagsExpanded] = React.useState(false);
|
||||
const [tagSearchQuery, setTagSearchQuery] = React.useState('');
|
||||
|
||||
const availableTags = React.useMemo(() => {
|
||||
const carnetNotes = allNotes.filter(n => n.carnetId === activeCarnetId);
|
||||
const tagsMap = new Map<string, Tag>();
|
||||
carnetNotes.forEach(note => {
|
||||
note.tags?.forEach(tag => {
|
||||
tagsMap.set(tag.id, tag);
|
||||
});
|
||||
});
|
||||
return Array.from(tagsMap.values()).sort((a, b) => {
|
||||
// AI tags first, then alphabetical
|
||||
if (a.type === 'ai' && b.type !== 'ai') return -1;
|
||||
if (a.type !== 'ai' && b.type === 'ai') return 1;
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
}, [allNotes, activeCarnetId]);
|
||||
|
||||
const visibleTags = React.useMemo(() => {
|
||||
let filtered = availableTags;
|
||||
if (tagSearchQuery) {
|
||||
filtered = availableTags.filter(t =>
|
||||
t.label.toLowerCase().includes(tagSearchQuery.toLowerCase())
|
||||
);
|
||||
} else if (!isTagsExpanded) {
|
||||
filtered = availableTags.slice(0, 10);
|
||||
// Ensure selected tags are always visible even if not in the first 10
|
||||
selectedTagIds.forEach(id => {
|
||||
if (!filtered.find(t => t.id === id)) {
|
||||
const tag = availableTags.find(t => t.id === id);
|
||||
if (tag) filtered.push(tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
return filtered;
|
||||
}, [availableTags, isTagsExpanded, tagSearchQuery, selectedTagIds]);
|
||||
|
||||
const toggleTag = (tagId: string) => {
|
||||
if (selectedTagIds.includes(tagId)) {
|
||||
setSelectedTagIds(selectedTagIds.filter(id => id !== tagId));
|
||||
} else {
|
||||
setSelectedTagIds([...selectedTagIds, tagId]);
|
||||
}
|
||||
};
|
||||
|
||||
if (!activeNoteId) {
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-y-auto">
|
||||
<header className="px-12 pt-12 pb-8 flex flex-col gap-6 sticky top-0 bg-paper/80 backdrop-blur-md z-30">
|
||||
<div className="flex justify-between items-start">
|
||||
<h1 className="text-4xl font-serif font-medium tracking-tight text-ink leading-tight pr-12">
|
||||
{activeCarnet?.name} — {filteredNotes[0]?.date || 'Oct 26'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between border-b border-ink/5 pb-4">
|
||||
<div className="flex items-center gap-6">
|
||||
<button
|
||||
onClick={() => setShowNewNoteModal(true)}
|
||||
className="flex items-center gap-2 text-[13px] text-ink font-medium hover:opacity-70 transition-opacity"
|
||||
>
|
||||
<Plus size={16} />
|
||||
<span>Add Note</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowNewCarnetModal(true, activeCarnetId)}
|
||||
className="flex items-center gap-2 text-[13px] text-concrete font-medium hover:text-ink transition-all"
|
||||
>
|
||||
<BookOpen size={16} />
|
||||
<span>New Sub-Carnet</span>
|
||||
</button>
|
||||
<button className="flex items-center gap-2 text-[13px] text-ink font-medium hover:opacity-70 transition-opacity">
|
||||
<Search size={16} />
|
||||
<span>Search</span>
|
||||
</button>
|
||||
</div>
|
||||
<button className="flex items-center gap-2 text-[13px] text-ink font-medium hover:opacity-70 transition-opacity">
|
||||
<Share2 size={16} />
|
||||
<span>Share</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 text-[10px] font-bold uppercase tracking-[0.2em] text-concrete">
|
||||
<TagIcon size={12} />
|
||||
<span>Filter by Tags</span>
|
||||
{selectedTagIds.length > 0 && (
|
||||
<span className="bg-blueprint/10 text-blueprint px-2 py-0.5 rounded-full text-[9px] lowercase tracking-normal">
|
||||
{selectedTagIds.length} active
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{availableTags.length > 10 && (
|
||||
<div className="relative group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tags..."
|
||||
className="bg-transparent border-b border-border/40 text-[10px] outline-none focus:border-blueprint/40 py-1 px-2 w-32 transition-all focus:w-48 placeholder:text-concrete/40"
|
||||
onChange={(e) => setTagSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2 items-center min-h-[32px]">
|
||||
<AnimatePresence mode="popLayout">
|
||||
{visibleTags.map(tag => {
|
||||
const isActive = selectedTagIds.includes(tag.id);
|
||||
return (
|
||||
<motion.button
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
key={tag.id}
|
||||
onClick={() => toggleTag(tag.id)}
|
||||
className={`px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-wider transition-all border flex items-center gap-2
|
||||
${isActive
|
||||
? 'bg-ink text-paper border-ink shadow-lg shadow-ink/10'
|
||||
: 'bg-white/40 border-border text-concrete hover:border-concrete/40 hover:bg-white/60'}`}
|
||||
>
|
||||
{tag.type === 'ai' && (
|
||||
<Sparkles
|
||||
size={10}
|
||||
className={isActive ? 'text-blueprint' : 'text-blueprint/60'}
|
||||
/>
|
||||
)}
|
||||
{tag.label}
|
||||
{isActive && <X size={10} />}
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
|
||||
{availableTags.length > 10 && !tagSearchQuery && (
|
||||
<button
|
||||
onClick={() => setIsTagsExpanded(!isTagsExpanded)}
|
||||
className="px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-concrete/60 hover:text-ink transition-colors border border-dashed border-border rounded-full"
|
||||
>
|
||||
{isTagsExpanded ? 'Show less' : `+ ${availableTags.length - 10} more`}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{selectedTagIds.length > 0 && (
|
||||
<button
|
||||
onClick={() => setSelectedTagIds([])}
|
||||
className="px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-rust hover:underline ml-auto"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="px-12 flex-1 pb-20">
|
||||
<div className="max-w-3xl space-y-16">
|
||||
{filteredNotes.map((note, index) => (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 * index, duration: 0.8 }}
|
||||
key={note.id}
|
||||
className="space-y-4 group cursor-pointer relative"
|
||||
onClick={() => setActiveNoteId(note.id)}
|
||||
>
|
||||
<h2 className="text-2xl font-serif font-medium text-ink flex items-center justify-between">
|
||||
<span className="flex items-center gap-3">
|
||||
{note.isPinned && <Pin size={18} className="text-amber-500 fill-amber-500" />}
|
||||
{note.title}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
togglePin(note.id);
|
||||
}}
|
||||
className={`p-2 rounded-full transition-all ${note.isPinned ? 'text-amber-600 bg-amber-50' : 'opacity-0 group-hover:opacity-40 hover:bg-slate-100 text-ink'}`}
|
||||
>
|
||||
<Pin size={16} />
|
||||
</button>
|
||||
<button className="opacity-0 group-hover:opacity-40 transition-opacity">
|
||||
<ChevronRight size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</h2>
|
||||
<div className="flex flex-col md:flex-row gap-8 items-start">
|
||||
<div className="w-full md:w-56 aspect-[4/3] bg-white/50 dark:bg-white/5 border border-border overflow-hidden rounded shadow-sm flex-shrink-0">
|
||||
<img
|
||||
src={note.imageUrl}
|
||||
alt={note.title}
|
||||
className="w-full h-full object-cover mix-blend-multiply opacity-80 grayscale contrast-125 hover:grayscale-0 hover:opacity-100 transition-all duration-500"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-wrap gap-2 mb-2">
|
||||
{note.tags?.map(tag => (
|
||||
<div
|
||||
key={tag.id}
|
||||
className={`px-2 py-0.5 rounded text-[9px] font-bold uppercase tracking-wider border flex items-center gap-1.5
|
||||
${tag.type === 'ai'
|
||||
? 'bg-blueprint/5 border-blueprint/20 text-blueprint'
|
||||
: 'bg-concrete/5 border-border text-concrete'}`}
|
||||
>
|
||||
{tag.type === 'ai' && <Sparkles size={8} />}
|
||||
{tag.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-[14px] leading-relaxed text-ink/80 font-light max-w-lg line-clamp-4">
|
||||
{note.content}
|
||||
</p>
|
||||
<span className="text-[11px] text-muted-ink uppercase tracking-widest font-medium">Read more</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
{filteredNotes.length === 0 && (
|
||||
<div className="h-64 flex flex-col items-center justify-center text-center space-y-4">
|
||||
<p className="font-serif text-xl italic text-muted-ink">This notebook is waiting for its first vision.</p>
|
||||
<button
|
||||
onClick={() => setShowNewNoteModal(true)}
|
||||
className="px-6 py-2 border border-ink text-[13px] uppercase tracking-[0.2em] hover:bg-ink hover:text-paper transition-all"
|
||||
>
|
||||
Begin Drawing
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer className="px-12 py-6 border-t border-ink/5 text-center mt-auto">
|
||||
<p className="text-[11px] text-muted-ink uppercase tracking-[0.2em] font-medium">
|
||||
© 2024 Architectural Grid. All rights reserved.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex overflow-hidden transition-all duration-500">
|
||||
<div className="flex-1 flex flex-col overflow-y-auto bg-white dark:bg-paper">
|
||||
<div className="px-12 py-8 flex items-center justify-between sticky top-0 bg-white/90 dark:bg-paper/90 backdrop-blur-sm z-40 border-b border-border">
|
||||
<button
|
||||
onClick={() => setActiveNoteId(null)}
|
||||
className="flex items-center gap-2 text-ink hover:opacity-60 transition-opacity"
|
||||
>
|
||||
<ArrowLeft size={18} />
|
||||
<span className="text-sm font-medium">Back to collection</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => togglePin(activeNoteId!)}
|
||||
className={`p-2 rounded-full transition-all ${activeNote?.isPinned ? 'text-amber-600 bg-amber-50 dark:bg-ochre/10' : 'text-muted-ink hover:text-ink'}`}
|
||||
title={activeNote?.isPinned ? "Unpin note" : "Pin note"}
|
||||
>
|
||||
<Pin size={18} className={activeNote?.isPinned ? 'fill-amber-600' : ''} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsAISidebarOpen(!isAISidebarOpen)}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-full border transition-all duration-300
|
||||
${isAISidebarOpen ? 'bg-ink text-paper border-ink' : 'border-border text-ink hover:bg-white/50 dark:hover:bg-white/5'}`}
|
||||
>
|
||||
<Sparkles size={16} />
|
||||
<span className="text-xs font-medium">AI Assistant</span>
|
||||
</button>
|
||||
<button className="p-2 text-muted-ink hover:text-ink transition-colors">
|
||||
<Share2 size={18} />
|
||||
</button>
|
||||
<button className="p-2 text-muted-ink hover:text-ink transition-colors">
|
||||
<MoreVertical size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto w-full px-12 py-16 space-y-12">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3 text-[12px] text-muted-ink uppercase tracking-[.25em] font-bold">
|
||||
<span>{activeCarnet?.name}</span>
|
||||
<ChevronRight size={10} />
|
||||
<span>{activeNote?.date}</span>
|
||||
</div>
|
||||
<h1 className="text-5xl md:text-6xl font-serif font-bold text-ink leading-tight">
|
||||
{activeNote?.title}
|
||||
</h1>
|
||||
<div className="flex flex-wrap gap-2 pt-2">
|
||||
{activeNote?.tags?.map(tag => (
|
||||
<div
|
||||
key={tag.id}
|
||||
className={`px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-widest border flex items-center gap-2
|
||||
${tag.type === 'ai'
|
||||
? 'bg-blueprint/5 border-blueprint/20 text-blueprint'
|
||||
: 'bg-paper border-border text-concrete'}`}
|
||||
>
|
||||
{tag.type === 'ai' && <Sparkles size={12} />}
|
||||
{tag.label}
|
||||
{tag.type === 'ai' && (
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-blueprint animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="aspect-[16/9] w-full bg-slate-100 dark:bg-white/5 rounded-xl overflow-hidden shadow-xl">
|
||||
<img
|
||||
src={activeNote?.imageUrl}
|
||||
alt={activeNote?.title}
|
||||
className="w-full h-full object-cover grayscale contrast-110"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="max-w-2xl mx-auto space-y-8 pb-32">
|
||||
<p className="text-xl md:text-2xl font-serif leading-relaxed text-ink italic">
|
||||
{activeNote?.content.split('.')[0]}.
|
||||
</p>
|
||||
<div className="h-px bg-border w-32" />
|
||||
<p className="text-lg leading-relaxed text-ink/80 font-light space-y-4 text-justify whitespace-pre-line">
|
||||
{activeNote?.content}
|
||||
{activeNote?.id.startsWith('n-') && (
|
||||
<>
|
||||
<br /><br />
|
||||
Architectural grids serve as the invisible scaffolding upon which spatial experiences are constructed. Beyond mere structural repetition, they facilitate a rhythmic dialogue between materiality and void. In this exploration, we examine how light fractures these rigid boundaries, creating a dynamic interplay that evolves with the passage of time.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import React from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { SettingsTab } from '../types';
|
||||
import { SettingsHeader } from './settings/SettingsHeader';
|
||||
import { GeneralTab } from './settings/GeneralTab';
|
||||
import { AITab } from './settings/AITab';
|
||||
import { AppearanceTab } from './settings/AppearanceTab';
|
||||
|
||||
interface SettingsViewProps {
|
||||
activeSettingsTab: SettingsTab;
|
||||
setActiveSettingsTab: (tab: SettingsTab) => void;
|
||||
}
|
||||
|
||||
export const SettingsView: React.FC<SettingsViewProps> = ({
|
||||
activeSettingsTab,
|
||||
setActiveSettingsTab
|
||||
}) => {
|
||||
return (
|
||||
<div className="h-full flex flex-col bg-paper dark:bg-dark-paper overflow-y-auto custom-scrollbar relative font-sans">
|
||||
<div className="absolute inset-0 opacity-[0.04] pointer-events-none grainy-bg mix-blend-multiply dark:mix-blend-overlay" />
|
||||
|
||||
<div className="relative z-10 flex flex-col min-h-full">
|
||||
<SettingsHeader
|
||||
activeTab={activeSettingsTab}
|
||||
setActiveTab={setActiveSettingsTab}
|
||||
/>
|
||||
|
||||
<div className="flex-1 px-12 pb-24 h-full">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<AnimatePresence mode="wait">
|
||||
{activeSettingsTab === 'general' && (
|
||||
<GeneralTab key="general" />
|
||||
)}
|
||||
|
||||
{activeSettingsTab === 'ai' && (
|
||||
<AITab key="ai" />
|
||||
)}
|
||||
|
||||
{activeSettingsTab === 'appearance' && (
|
||||
<AppearanceTab key="appearance" />
|
||||
)}
|
||||
|
||||
{['profile', 'data', 'mcp', 'about'].includes(activeSettingsTab) && (
|
||||
<motion.div
|
||||
key="placeholder"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="h-[50vh] flex flex-col items-center justify-center border border-dashed border-border rounded-[32px] space-y-6 bg-white/20 dark:bg-white/5"
|
||||
>
|
||||
<div className="w-16 h-16 rounded-3xl border border-dashed border-concrete/20 flex items-center justify-center text-concrete/40 bg-paper/50">
|
||||
<span className="text-2xl font-serif italic text-concrete">?</span>
|
||||
</div>
|
||||
<div className="text-center space-y-1">
|
||||
<p className="text-ink font-bold text-sm tracking-tight">Section en développement</p>
|
||||
<p className="text-concrete italic text-[11px] font-light">Le module {activeSettingsTab} sera disponible prochainement.</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,350 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Plus,
|
||||
Archive,
|
||||
Settings,
|
||||
ChevronRight,
|
||||
BookOpen,
|
||||
Bot,
|
||||
Microscope,
|
||||
Activity,
|
||||
Pin,
|
||||
Moon,
|
||||
Sun,
|
||||
Bell,
|
||||
Lock
|
||||
} from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { NavigationView, Carnet, Note } from '../types';
|
||||
|
||||
interface NoteLinkProps {
|
||||
note: Note;
|
||||
isActive: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const NoteLink: React.FC<NoteLinkProps> = ({ note, isActive, onClick }) => (
|
||||
<motion.button
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
onClick={onClick}
|
||||
className={`w-full flex items-center gap-2 pl-12 pr-4 py-2 text-[12px] transition-colors rounded-lg
|
||||
${isActive ? 'bg-white/50 dark:bg-white/10 text-ink font-medium' : 'text-muted-ink hover:text-ink hover:bg-white/30'}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1 truncate">
|
||||
<div className={`w-1.5 h-1.5 rounded-full shrink-0 ${isActive ? 'bg-ink' : 'bg-transparent border border-muted-ink/30'}`} />
|
||||
<span className="truncate">{note.title}</span>
|
||||
</div>
|
||||
{note.isPinned && <Pin size={10} className="text-amber-500 shrink-0" />}
|
||||
</motion.button>
|
||||
);
|
||||
|
||||
interface SidebarItemProps {
|
||||
carnet: Carnet;
|
||||
isActive: boolean;
|
||||
notes: Note[];
|
||||
activeNoteId: string | null;
|
||||
onCarnetClick: () => void;
|
||||
onNoteClick: (noteId: string) => void;
|
||||
onAddSubCarnet: () => void;
|
||||
children?: React.ReactNode;
|
||||
level: number;
|
||||
isExpanded: boolean;
|
||||
toggleExpand: () => void;
|
||||
}
|
||||
|
||||
const SidebarItem: React.FC<SidebarItemProps> = ({
|
||||
carnet,
|
||||
isActive,
|
||||
notes,
|
||||
activeNoteId,
|
||||
onCarnetClick,
|
||||
onNoteClick,
|
||||
onAddSubCarnet,
|
||||
children,
|
||||
level,
|
||||
isExpanded,
|
||||
toggleExpand
|
||||
}) => {
|
||||
const hasChildren = React.Children.count(children) > 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
<div
|
||||
className="flex items-center group relative h-10"
|
||||
style={{ paddingLeft: `${level * 12}px` }}
|
||||
>
|
||||
{/* Hierarchy Guide Line */}
|
||||
{level > 0 && (
|
||||
<div className="absolute left-[8px] top-[-10px] bottom-1/2 w-px bg-border/40" />
|
||||
)}
|
||||
{level > 0 && (
|
||||
<div className="absolute left-[8px] top-1/2 w-[8px] h-px bg-border/40" />
|
||||
)}
|
||||
|
||||
<div className="flex-1 flex items-center gap-1">
|
||||
{hasChildren ? (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleExpand();
|
||||
}}
|
||||
className="p-1 hover:bg-ink/5 dark:hover:bg-white/5 rounded-md transition-colors text-muted-ink"
|
||||
>
|
||||
<motion.div animate={{ rotate: isExpanded ? 90 : 0 }} transition={{ duration: 0.2 }}>
|
||||
<ChevronRight size={14} />
|
||||
</motion.div>
|
||||
</button>
|
||||
) : (
|
||||
<div className="w-6" /> // Spacer for alignment
|
||||
)}
|
||||
|
||||
<motion.button
|
||||
whileHover={{ x: 2 }}
|
||||
onClick={onCarnetClick}
|
||||
className={`flex-1 flex items-center gap-2.5 px-2 py-1.5 rounded-lg transition-all duration-300
|
||||
${isActive ? 'bg-white shadow-sm border border-border/40 dark:bg-white/10' : 'hover:bg-white/40 dark:hover:bg-white/5'}`}
|
||||
>
|
||||
<div className={`w-6 h-6 rounded-md flex items-center justify-center text-[10px] font-bold border transition-all
|
||||
${isActive ? 'bg-ink text-paper border-ink' : 'bg-paper dark:bg-white/10 text-concrete border-border dark:border-white/10'}`}>
|
||||
{carnet.initial}
|
||||
</div>
|
||||
<div className="flex-1 text-left flex items-center gap-2">
|
||||
<span className={`text-[12px] font-medium transition-colors truncate ${isActive ? 'text-ink' : 'text-muted-ink hover:text-ink'}`}>
|
||||
{carnet.name}
|
||||
</span>
|
||||
{carnet.isPrivate && <Lock size={10} className="text-concrete/60 shrink-0" />}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onAddSubCarnet();
|
||||
}}
|
||||
className="opacity-0 group-hover:opacity-100 p-1 hover:bg-ink/10 dark:hover:bg-white/10 rounded-md transition-all text-concrete hover:text-ink"
|
||||
title="Add sub-carnet"
|
||||
>
|
||||
<Plus size={10} />
|
||||
</button>
|
||||
|
||||
{notes.length > 0 && (
|
||||
<span className="text-[9px] font-bold text-concrete/40 px-1.5 border border-border/40 rounded-full group-hover:text-concrete transition-colors">
|
||||
{notes.length}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence initial={false}>
|
||||
{(isExpanded || (isActive && !hasChildren)) && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="relative" style={{ marginLeft: `${(level + 1) * 12 + 10}px` }}>
|
||||
{/* Vertical line for nested content */}
|
||||
<div className="absolute left-[-6px] top-0 bottom-4 w-px bg-border/30" />
|
||||
|
||||
<div className="space-y-1 py-1">
|
||||
{children}
|
||||
{isActive && !hasChildren && notes.map(note => (
|
||||
<NoteLink
|
||||
key={note.id}
|
||||
note={note}
|
||||
isActive={activeNoteId === note.id}
|
||||
onClick={() => onNoteClick(note.id)}
|
||||
/>
|
||||
))}
|
||||
{isActive && !hasChildren && notes.length === 0 && (
|
||||
<p className="pl-8 py-2 text-[10px] italic text-concrete/40 font-light">
|
||||
No notes found
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface SidebarProps {
|
||||
activeView: NavigationView;
|
||||
isDarkMode: boolean;
|
||||
setIsDarkMode: (val: boolean) => void;
|
||||
setActiveView: (view: NavigationView) => void;
|
||||
carnets: Carnet[];
|
||||
notes: Note[];
|
||||
activeCarnetId: string;
|
||||
activeNoteId: string | null;
|
||||
setActiveCarnetId: (id: string) => void;
|
||||
setActiveNoteId: (id: string | null) => void;
|
||||
setShowNewCarnetModal: (show: boolean, parentId?: string) => void;
|
||||
}
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = ({
|
||||
activeView,
|
||||
isDarkMode,
|
||||
setIsDarkMode,
|
||||
setActiveView,
|
||||
carnets,
|
||||
notes,
|
||||
activeCarnetId,
|
||||
activeNoteId,
|
||||
setActiveCarnetId,
|
||||
setActiveNoteId,
|
||||
setShowNewCarnetModal
|
||||
}) => {
|
||||
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set(['4'])); // Default expand Research
|
||||
|
||||
const toggleExpand = (id: string) => {
|
||||
const newSet = new Set(expandedIds);
|
||||
if (newSet.has(id)) newSet.delete(id);
|
||||
else newSet.add(id);
|
||||
setExpandedIds(newSet);
|
||||
};
|
||||
|
||||
const renderCarnetTree = (parentId: string | undefined = undefined, level: number = 0) => {
|
||||
return carnets
|
||||
.filter(c => c.parentId === parentId)
|
||||
.map(carnet => (
|
||||
<SidebarItem
|
||||
key={carnet.id}
|
||||
carnet={carnet}
|
||||
isActive={activeCarnetId === carnet.id}
|
||||
notes={notes.filter(n => n.carnetId === carnet.id)}
|
||||
activeNoteId={activeNoteId}
|
||||
level={level}
|
||||
isExpanded={expandedIds.has(carnet.id)}
|
||||
toggleExpand={() => toggleExpand(carnet.id)}
|
||||
onAddSubCarnet={() => {
|
||||
if (!expandedIds.has(carnet.id)) toggleExpand(carnet.id);
|
||||
setShowNewCarnetModal(true, carnet.id);
|
||||
}}
|
||||
onCarnetClick={() => {
|
||||
setActiveCarnetId(carnet.id);
|
||||
setActiveNoteId(null);
|
||||
// Auto expand when clicking
|
||||
if (!expandedIds.has(carnet.id)) toggleExpand(carnet.id);
|
||||
}}
|
||||
onNoteClick={(id) => {
|
||||
setActiveCarnetId(carnet.id);
|
||||
setActiveNoteId(id);
|
||||
}}
|
||||
>
|
||||
{renderCarnetTree(carnet.id, level + 1)}
|
||||
</SidebarItem>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="w-80 bg-white/30 dark:bg-[#151515] backdrop-blur-md border-r border-border p-6 flex flex-col z-20 shrink-0 transition-colors duration-500">
|
||||
<div className="mb-10 flex items-center justify-between">
|
||||
<div className="w-10 h-10 rounded-full bg-slate-200 dark:bg-white/10 border border-border flex items-center justify-center text-ink font-serif text-lg shadow-sm">
|
||||
A
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setIsDarkMode(!isDarkMode)}
|
||||
className="p-2 text-muted-ink hover:text-ink transition-all bg-white/50 dark:bg-white/10 rounded-full border border-border dark:border-white/10"
|
||||
>
|
||||
{isDarkMode ? <Sun size={14} /> : <Moon size={14} />}
|
||||
</button>
|
||||
|
||||
<button className="p-2 text-muted-ink hover:text-ink transition-all relative group bg-white/50 dark:bg-white/10 rounded-full border border-border dark:border-white/10">
|
||||
<Bell size={14} />
|
||||
<span className="absolute -top-1 -right-1 w-4 h-4 bg-rose-500 text-white text-[9px] font-bold flex items-center justify-center rounded-full border border-white shadow-sm">
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div className="flex bg-white/50 dark:bg-white/10 p-1 rounded-full border border-border dark:border-white/10 transition-all">
|
||||
<button
|
||||
onClick={() => setActiveView('notebooks')}
|
||||
className={`p-1.5 rounded-full transition-all ${activeView === 'notebooks' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink'}`}
|
||||
title="Carnets"
|
||||
>
|
||||
<BookOpen size={14} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveView('agents')}
|
||||
className={`p-1.5 rounded-full transition-all ${activeView === 'agents' ? 'bg-ink text-paper shadow-sm' : 'text-muted-ink hover:text-ink'}`}
|
||||
title="Agents"
|
||||
>
|
||||
<Bot size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto space-y-8 -mx-2 px-2 py-4 custom-scrollbar">
|
||||
{activeView === 'notebooks' ? (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<p className="text-[10px] font-bold text-concrete tracking-[0.2em] uppercase">
|
||||
Architecture Grid
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowNewCarnetModal(true)}
|
||||
className="p-1 hover:bg-paper dark:hover:bg-white/5 rounded-md text-concrete hover:text-ink transition-colors"
|
||||
title="New Carnet"
|
||||
>
|
||||
<Plus size={14} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="space-y-0.5">
|
||||
{renderCarnetTree()}
|
||||
</nav>
|
||||
</div>
|
||||
) : activeView === 'agents' ? (
|
||||
<div>
|
||||
<p className="text-[10px] font-bold text-muted-ink tracking-widest uppercase mb-4 px-4">
|
||||
Intelligence OS
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
{[
|
||||
{ id: 'a1', name: 'Mes Agents', icon: <Bot size={16} /> },
|
||||
{ id: 'a2', name: 'Le Lab AI', icon: <Microscope size={16} /> },
|
||||
{ id: 'a3', name: 'Activités', icon: <Activity size={16} /> },
|
||||
].map(item => (
|
||||
<button
|
||||
key={item.id}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-300 group
|
||||
${item.id === 'a1' ? 'active-nav-item' : 'text-muted-ink hover:bg-white/40 dark:hover:bg-white/5 hover:text-ink'}`}
|
||||
>
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center border transition-colors
|
||||
${item.id === 'a1' ? 'bg-ink text-paper border-ink' : 'bg-white/60 dark:bg-white/10 border-border dark:border-white/10 group-hover:border-ink/20'}`}>
|
||||
{item.icon}
|
||||
</div>
|
||||
<span className="text-[13px] font-medium">{item.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="pt-6 border-t border-border space-y-4">
|
||||
<button className="flex items-center gap-3 px-4 text-[13px] text-muted-ink hover:text-ink transition-colors font-medium group">
|
||||
<Archive size={16} className="text-muted-ink group-hover:text-ink" />
|
||||
<span>Archive</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveView('settings')}
|
||||
className={`flex items-center gap-3 px-4 text-[13px] transition-colors font-medium group ${activeView === 'settings' ? 'text-ink' : 'text-muted-ink hover:text-ink'}`}
|
||||
>
|
||||
<Settings size={16} className={activeView === 'settings' ? 'text-ink' : 'text-muted-ink group-hover:text-ink'} />
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
@@ -1,152 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Sparkles, Edit3, MessageCircle, Languages, Tag, History, FlaskConical } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
const AISettingCard = ({ icon, title, description, defaultChecked = false }: any) => (
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-6 flex items-center justify-between group hover:shadow-xl hover:shadow-blueprint/5 transition-all duration-300">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-paper dark:bg-blueprint/10 rounded-2xl text-blueprint group-hover:bg-blueprint group-hover:text-white group-hover:scale-110 transition-all duration-300 border border-blueprint/20">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-[13px] font-bold text-ink">{title}</h4>
|
||||
<p className="text-[10px] text-muted-ink leading-relaxed pr-4 line-clamp-2">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer shrink-0 ml-4">
|
||||
<input type="checkbox" className="sr-only peer" defaultChecked={defaultChecked} />
|
||||
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-blueprint"></div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const AITab: React.FC = () => {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-16 pb-20"
|
||||
>
|
||||
<div className="space-y-10">
|
||||
<h3 className="text-[10px] font-bold uppercase tracking-[0.4em] text-muted-ink opacity-60">Configurez vos fonctionnalités IA et préférences</h3>
|
||||
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-sm font-bold text-ink border-b border-border/40 pb-4">Fonctionnalités IA</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<AISettingCard
|
||||
icon={<Edit3 size={18} />}
|
||||
title="Suggestions de titre"
|
||||
description="Suggérer des titres pour les notes sans titre après 50+ mots"
|
||||
defaultChecked
|
||||
/>
|
||||
<AISettingCard
|
||||
icon={<Sparkles size={18} />}
|
||||
title="IA Note"
|
||||
description="Active le bouton de chat IA et les outils d'amélioration du texte"
|
||||
defaultChecked
|
||||
/>
|
||||
<AISettingCard
|
||||
icon={<MessageCircle size={18} />}
|
||||
title="💡 J'ai remarqué quelque chose..."
|
||||
description="Aperçu quotidien de vos notes"
|
||||
defaultChecked
|
||||
/>
|
||||
<AISettingCard
|
||||
icon={<Languages size={18} />}
|
||||
title="Détection de langue"
|
||||
description="Détecte automatiquement la langue de vos notes"
|
||||
defaultChecked
|
||||
/>
|
||||
<AISettingCard
|
||||
icon={<Tag size={18} />}
|
||||
title="Suggestion des labels"
|
||||
description="Suggère et applique des étiquettes automatiquement à vos notes"
|
||||
defaultChecked
|
||||
/>
|
||||
<AISettingCard
|
||||
icon={<History size={18} />}
|
||||
title="Historique des notes"
|
||||
description="Active les snapshots de versions et la restauration depuis History"
|
||||
defaultChecked
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 pt-6">
|
||||
{/* Fréquence */}
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 space-y-10">
|
||||
<div className="space-y-1.5 text-left text-blueprint">
|
||||
<h4 className="text-sm font-bold">Fréquence</h4>
|
||||
<p className="text-[10px] opacity-60 uppercase tracking-wider font-semibold">Fréquence d'analyse des connexions</p>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<label className="flex items-center gap-4 cursor-pointer group">
|
||||
<input type="radio" name="freq" className="sr-only peer" defaultChecked />
|
||||
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
|
||||
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-ink group-hover:opacity-70 transition-opacity">Quotidienne</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-4 cursor-pointer group">
|
||||
<input type="radio" name="freq" className="sr-only peer" />
|
||||
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
|
||||
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-ink group-hover:opacity-70 transition-opacity">Hebdomadaire</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mode d'historique */}
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 space-y-10">
|
||||
<div className="space-y-1.5 text-left text-blueprint">
|
||||
<h4 className="text-sm font-bold">Mode d'historique</h4>
|
||||
<p className="text-[10px] opacity-60 uppercase tracking-wider font-semibold">Gestion des snapshots</p>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<label className="flex items-center gap-4 cursor-pointer group">
|
||||
<input type="radio" name="hist" className="sr-only peer" defaultChecked />
|
||||
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
|
||||
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm font-bold text-ink">Manuel (bouton commit)</p>
|
||||
<p className="text-[10px] text-muted-ink">Créer des snapshots manuellement</p>
|
||||
</div>
|
||||
</label>
|
||||
<label className="flex items-center gap-4 cursor-pointer group">
|
||||
<input type="radio" name="hist" className="sr-only peer" />
|
||||
<div className="w-5 h-5 rounded-full border-2 border-border peer-checked:border-blueprint flex items-center justify-center p-0.5 transition-all">
|
||||
<div className="w-full h-full rounded-full bg-transparent peer-checked:bg-blueprint" />
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm font-bold text-ink">Automatique (intelligent)</p>
|
||||
<p className="text-[10px] text-muted-ink">Snapshots automatiques avec détection</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mode Démo */}
|
||||
<div className="bg-ochre/5 dark:bg-ochre/10 border border-ochre/20 rounded-2xl p-8 flex items-center justify-between group transition-all duration-300 hover:bg-ochre/10">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="p-3 bg-paper dark:bg-ochre/20 rounded-2xl text-ochre border border-ochre/30">
|
||||
<FlaskConical size={20} />
|
||||
</div>
|
||||
<div className="space-y-1.5 text-left">
|
||||
<h4 className="text-sm font-bold text-ink flex items-center gap-3">
|
||||
🧪 Mode Démo
|
||||
</h4>
|
||||
<p className="text-[11px] text-muted-ink leading-relaxed font-medium">Accélère Memory Echo pour les tests. Les connexions apparaissent instantanément.</p>
|
||||
</div>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer shrink-0 ml-4">
|
||||
<input type="checkbox" className="sr-only peer" />
|
||||
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-ochre"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Palette, Type, LayoutGrid, Maximize } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
const AppearanceSelect = ({ icon, title, description, options, defaultValue }: any) => (
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 space-y-8 group transition-all duration-300 hover:shadow-xl hover:shadow-slate/5">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-slate border border-border group-hover:scale-110 transition-transform duration-300">
|
||||
{icon}
|
||||
</div>
|
||||
<div className="space-y-0.5 text-left">
|
||||
<h4 className="text-base font-bold text-ink">{title}</h4>
|
||||
<p className="text-[11px] text-concrete leading-tight">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative group/select">
|
||||
<select
|
||||
defaultValue={defaultValue}
|
||||
className="w-full bg-white/50 dark:bg-black/40 border border-border rounded-xl px-5 py-4 text-sm outline-none focus:ring-1 ring-slate/20 appearance-none cursor-pointer text-ink font-bold transition-all hover:bg-white dark:hover:bg-black/60"
|
||||
>
|
||||
{options.map((opt: string) => (
|
||||
<option key={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-concrete group-hover/select:text-slate transition-colors">
|
||||
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const AppearanceTab: React.FC = () => {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-16 pb-20"
|
||||
>
|
||||
<div className="space-y-10">
|
||||
<h3 className="text-[10px] font-bold uppercase tracking-[0.4em] text-concrete opacity-60">Personnaliser l'apparence de l'application</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<AppearanceSelect
|
||||
icon={<Palette size={20} />}
|
||||
title="Thème"
|
||||
description="Sélectionner le mode visuel"
|
||||
options={['Clair', 'Sombre', 'Système']}
|
||||
defaultValue="Clair"
|
||||
/>
|
||||
<AppearanceSelect
|
||||
icon={<Type size={20} />}
|
||||
title="Taille de la police"
|
||||
description="Ajustez la lisibilité globale de l'interface"
|
||||
options={['Petite', 'Moyenne', 'Grande']}
|
||||
defaultValue="Moyenne"
|
||||
/>
|
||||
<AppearanceSelect
|
||||
icon={<Type size={20} />}
|
||||
title="Famille de polices"
|
||||
description="La typographie définit l'âme de l'application"
|
||||
options={['Inter', 'JetBrains Mono', 'Public Sans', 'Outfit']}
|
||||
defaultValue="JetBrains Mono"
|
||||
/>
|
||||
<AppearanceSelect
|
||||
icon={<LayoutGrid size={20} />}
|
||||
title="Affichage des notes"
|
||||
description="Gestion visuelle de la grille de composition"
|
||||
options={['Cartes (grille)', 'Liste', 'Tableau']}
|
||||
defaultValue="Cartes (grille)"
|
||||
/>
|
||||
<AppearanceSelect
|
||||
icon={<Maximize size={20} />}
|
||||
title="Taille des notes"
|
||||
description="Structure de la mise en page des éléments"
|
||||
options={['Taille uniforme', 'Variable (Masonry)']}
|
||||
defaultValue="Taille uniforme"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Globe, Bell } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
export const GeneralTab: React.FC = () => {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-12"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-[10px] font-bold uppercase tracking-[0.3em] text-concrete">Paramètres généraux de l'application</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Langue */}
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-xl p-8 space-y-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-slate border border-border">
|
||||
<Globe size={18} />
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<h4 className="text-base font-bold text-ink">Langue</h4>
|
||||
<p className="text-[11px] text-concrete">Sélectionner une langue</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative group">
|
||||
<select className="w-full bg-white/50 dark:bg-black/40 border border-border rounded-xl px-5 py-3.5 text-sm outline-none focus:ring-1 ring-blueprint/20 appearance-none cursor-pointer transition-all hover:bg-white dark:hover:bg-black/60 text-ink font-medium">
|
||||
<option>Français</option>
|
||||
<option>English</option>
|
||||
<option>Español</option>
|
||||
</select>
|
||||
<div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none opacity-40 text-concrete">
|
||||
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notifications */}
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-xl p-8 space-y-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-slate border border-border">
|
||||
<Bell size={18} />
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<h4 className="text-base font-bold text-ink">Notifications</h4>
|
||||
<p className="text-[11px] text-concrete">Gérez vos préférences de notifications</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 divide-y divide-border/40 text-left">
|
||||
<div className="flex items-center justify-between pt-0">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-bold text-ink">Notifications par email</p>
|
||||
<p className="text-[10px] text-concrete leading-relaxed">Recevoir des notifications importantes par email</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" className="sr-only peer" />
|
||||
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-slate"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-6">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-bold text-ink">Notifications bureau</p>
|
||||
<p className="text-[10px] text-concrete leading-relaxed">Recevoir des notifications dans votre navigateur</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" className="sr-only peer" defaultChecked />
|
||||
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-slate"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Settings, Sparkles, Palette, User, Database, Code, Info } from 'lucide-react';
|
||||
import { motion } from 'motion/react';
|
||||
import { SettingsTab } from '../../types';
|
||||
|
||||
interface SettingsHeaderProps {
|
||||
activeTab: SettingsTab;
|
||||
setActiveTab: (tab: SettingsTab) => void;
|
||||
}
|
||||
|
||||
export const SettingsHeader: React.FC<SettingsHeaderProps> = ({ activeTab, setActiveTab }) => {
|
||||
const tabs = [
|
||||
{ id: 'general', label: 'Paramètres généraux', icon: <Settings size={14} /> },
|
||||
{ id: 'ai', label: 'Paramètres IA', icon: <Sparkles size={14} /> },
|
||||
{ id: 'appearance', label: 'Apparence', icon: <Palette size={14} /> },
|
||||
{ id: 'profile', label: 'Profil', icon: <User size={14} /> },
|
||||
{ id: 'data', label: 'Gestion des données', icon: <Database size={14} /> },
|
||||
{ id: 'mcp', label: 'Paramètres MCP', icon: <Code size={14} /> },
|
||||
{ id: 'about', label: 'À propos', icon: <Info size={14} /> },
|
||||
];
|
||||
|
||||
return (
|
||||
<header className="px-12 pt-20 pb-16 space-y-12">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-[64px] font-serif text-ink tracking-tight leading-none italic font-medium">Paramètres</h1>
|
||||
<p className="text-[10px] font-bold uppercase tracking-[0.4em] text-concrete opacity-60">Configuration & Préférences</p>
|
||||
</div>
|
||||
|
||||
<nav className="flex items-center gap-1 border-b border-border/40 pb-px">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id as SettingsTab)}
|
||||
className={`flex items-center gap-2.5 px-6 py-5 text-[10px] font-bold uppercase tracking-[0.18em] transition-all relative whitespace-nowrap
|
||||
${activeTab === tab.id ? 'text-ink' : 'text-concrete hover:text-ink/60'}`}
|
||||
>
|
||||
<span className={activeTab === tab.id ? 'text-ink' : 'text-concrete'}>{tab.icon}</span>
|
||||
{tab.label}
|
||||
{activeTab === tab.id && (
|
||||
<motion.div
|
||||
layoutId="activeSettingsTabLine"
|
||||
className="absolute bottom-0 left-0 right-0 h-0.5 bg-ink"
|
||||
transition={{ type: 'spring', bounce: 0.1, duration: 0.8 }}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user