Briefing granulaire, pistes rapides puis enrichissement async, layout persisté v5, suggestions agents, intégration Gmail et navigation sidebar alignée sur /home. Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
3.5 KiB
TypeScript
127 lines
3.5 KiB
TypeScript
'use client'
|
|
|
|
import { motion } from 'motion/react'
|
|
import { Inbox, GraduationCap, Bell, Sparkles, Layers } from 'lucide-react'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
|
|
export interface DashboardActionStripProps {
|
|
inboxCount: number
|
|
dueFlashcards: number
|
|
reminderCount: number
|
|
discoveryCount: number
|
|
themeCount: number
|
|
inboxPulse?: number
|
|
onInbox: () => void
|
|
onReview: () => void
|
|
onReminders: () => void
|
|
onDiscoveries: () => void
|
|
onThemes: () => void
|
|
prefersReducedMotion?: boolean
|
|
}
|
|
|
|
export function DashboardActionStrip({
|
|
inboxCount,
|
|
dueFlashcards,
|
|
reminderCount,
|
|
discoveryCount,
|
|
themeCount,
|
|
inboxPulse = 0,
|
|
onInbox,
|
|
onReview,
|
|
onReminders,
|
|
onDiscoveries,
|
|
onThemes,
|
|
prefersReducedMotion,
|
|
}: DashboardActionStripProps) {
|
|
const { t } = useLanguage()
|
|
|
|
const items = [
|
|
{
|
|
key: 'inbox',
|
|
icon: Inbox,
|
|
label: t('homeDashboard.inbox'),
|
|
value: inboxCount,
|
|
onClick: onInbox,
|
|
accent: inboxCount > 0,
|
|
pulse: inboxPulse > 0,
|
|
},
|
|
{
|
|
key: 'review',
|
|
icon: GraduationCap,
|
|
label: t('homeDashboard.review'),
|
|
value: dueFlashcards,
|
|
onClick: onReview,
|
|
accent: dueFlashcards > 0,
|
|
},
|
|
{
|
|
key: 'reminders',
|
|
icon: Bell,
|
|
label: t('homeDashboard.reminders'),
|
|
value: reminderCount,
|
|
onClick: onReminders,
|
|
accent: reminderCount > 0,
|
|
},
|
|
{
|
|
key: 'discoveries',
|
|
icon: Sparkles,
|
|
label: t('homeDashboard.aiFound'),
|
|
value: discoveryCount,
|
|
onClick: onDiscoveries,
|
|
accent: discoveryCount > 0,
|
|
},
|
|
{
|
|
key: 'themes',
|
|
icon: Layers,
|
|
label: t('homeDashboard.themes'),
|
|
value: themeCount,
|
|
onClick: onThemes,
|
|
accent: themeCount > 0,
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div className="flex gap-2 overflow-x-auto custom-scrollbar pb-0.5 -mx-0.5 px-0.5">
|
|
{items.map(item => {
|
|
const Icon = item.icon
|
|
const Wrapper = item.pulse && !prefersReducedMotion ? motion.button : 'button'
|
|
const motionProps = item.pulse && !prefersReducedMotion
|
|
? {
|
|
key: `inbox-pulse-${inboxPulse}`,
|
|
animate: { scale: [1, 1.03, 1] },
|
|
transition: { duration: 0.45 },
|
|
}
|
|
: {}
|
|
return (
|
|
<Wrapper
|
|
key={item.key}
|
|
type="button"
|
|
onClick={item.onClick}
|
|
{...motionProps}
|
|
className={`shrink-0 flex items-center gap-2.5 px-3.5 py-2.5 rounded-xl border transition-all text-start min-w-[108px] ${
|
|
item.accent
|
|
? 'border-brand-accent/30 bg-brand-accent/[0.06] hover:border-brand-accent/50 hover:bg-brand-accent/10 shadow-sm'
|
|
: 'border-border/25 bg-white/60 dark:bg-zinc-900/40 hover:border-border/50'
|
|
}`}
|
|
>
|
|
<div className={`w-7 h-7 rounded-lg flex items-center justify-center shrink-0 ${
|
|
item.accent ? 'bg-brand-accent/15 text-brand-accent' : 'bg-stone-100 dark:bg-zinc-800 text-concrete'
|
|
}`}>
|
|
<Icon size={13} strokeWidth={2} />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className={`text-lg font-serif font-bold leading-none ${
|
|
item.accent ? 'text-ink dark:text-dark-ink' : 'text-concrete/80'
|
|
}`}>
|
|
{item.value}
|
|
</p>
|
|
<p className="text-[8px] font-mono font-bold uppercase tracking-wider text-concrete truncate mt-0.5">
|
|
{item.label}
|
|
</p>
|
|
</div>
|
|
</Wrapper>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|