// 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, });
{subtitle}