// components.jsx — shared UI components const { useState, useEffect, useMemo, useRef, useCallback } = React; const t = DOPO.I18N.it; // ─── Icons ────────────────────────────────────────────────────────────────── function Icon({ name, size = 18, color = 'currentColor', strokeWidth = 1.6, style: extraStyle }) { const s = { width: size, height: size, display: 'inline-block', flexShrink: 0, ...extraStyle }; const props = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round', style: s }; const paths = { home: <>, inventory: <>, memories: <>, circle: <>, bequests: <>, profile: <>, search: <>, plus: <>, arrow_right: <>, close: <>, check: <>, chevron_right: <>, chevron_down: <>, dot_menu: <>, filter: <>, letter: <>, audio: <>, photo: <>, video: <>, doc: <>, service: <>, shield: <>, mail: <>, lock: <>, clock: <>, oblio: <>, eye_off: <>, people: <>, tag: <>, sparkle: <>, bell: <>, settings: <>, edit: <>, trash: <>, heart: <>, gift: <>, alert: <>, key: <>, book: <>, }; return ( {paths[name] || } ); } // ─── Monogram ──────────────────────────────────────────────────────────────── function Monogram({ initials, color, size = 'md', style: extraStyle = {} }) { const cls = size === 'sm' ? 'mono mono-sm' : size === 'lg' ? 'mono mono-lg' : size === 'xl' ? 'mono mono-xl' : 'mono'; return (
{initials}
); } // ─── MonoStack ─────────────────────────────────────────────────────────────── function MonoStack({ ids, people, max = 3 }) { const shown = ids.slice(0, max); const rest = ids.length - max; const pMap = Object.fromEntries((people || DOPO.SEED.people).map(p => [p.id, p])); return (
{shown.map(id => { const p = pMap[id]; if (!p) return null; return ; })} {rest > 0 && (
+{rest}
)}
); } // ─── DestinyPill ───────────────────────────────────────────────────────────── function DestinyPill({ destiny, onClick }) { const labels = { people: 'Persone', oblio: 'Oblio', hidden: 'Nascosto', null: t.common.not_set, undefined: t.common.not_set, }; const label = labels[destiny] || t.common.not_set; const d = destiny || 'none'; return ( {label} ); } // ─── ServiceBadge ──────────────────────────────────────────────────────────── function ServiceBadge({ icon, color, size = 36 }) { return (
{icon}
); } // ─── StatCell ──────────────────────────────────────────────────────────────── function StatCell({ num, label, accent = false }) { return (
{num}
{label}
); } // ─── Sidebar ───────────────────────────────────────────────────────────────── function Sidebar({ route, setRoute, data }) { const nav = t.nav; const seed = data || DOPO.SEED; const unassigned = seed.services.filter(s => !s.destiny).length + seed.assets.filter(a => !a.destiny).length; const links = [ { id: 'home', label: nav.home, icon: 'home' }, { id: 'inventory', label: nav.inventory, icon: 'inventory', count: seed.services.length + seed.assets.length }, { id: 'memories', label: nav.memories, icon: 'memories', count: seed.memories.length }, { id: 'circle', label: nav.circle, icon: 'circle', count: seed.people.length }, { id: 'bequests', label: nav.bequests, icon: 'bequests', count: seed.bequests.length }, { id: 'profile', label: nav.profile, icon: 'profile' }, ]; const progress = seed.profileProgress || 62; return ( ); } // ─── PageHeader ────────────────────────────────────────────────────────────── function PageHeader({ eyebrow, title, subtitle, actions, serif = true }) { return (
{eyebrow &&
{eyebrow}
}

{title}

{actions &&
{actions}
}
{subtitle && (

{subtitle}

)}
); } // ─── SectionHeader ─────────────────────────────────────────────────────────── function SectionHeader({ title, action }) { return (
{title}
{action}
); } // Export all to window Object.assign(window, { Icon, Monogram, MonoStack, DestinyPill, ServiceBadge, StatCell, Sidebar, PageHeader, SectionHeader, });