/* settings-atoms.jsx — reusable form atoms for the settings page. All components expect plain values + onChange callbacks (controlled mode). */ // ── Card with gradient accent + header ────────────────────────────────── const SCard = ({ accent, iconBg, iconColor, iconName, title, subtitle, right, children, style }) => (

{title}

{subtitle &&
{subtitle}
}
{right &&
{right}
}
{children}
); // ── Generic text/number input ─────────────────────────────────────────── const SField = ({label, help, value, onChange, type='text', unit, placeholder, readOnly, disabled}) => { const handle = (e) => { if (readOnly || disabled) return; if (type === 'number'){ const v = e.target.value; if (v === '' || v === '-') { onChange(v); return; } const n = parseFloat(v); onChange(isNaN(n) ? 0 : n); } else { onChange(e.target.value); } }; return (
{label && }
{unit && {unit}}
{help &&
{help}
}
); }; // ── Select ────────────────────────────────────────────────────────────── const SSelect = ({label, help, value, onChange, options}) => (
{label && } {help &&
{help}
}
); // ── Pill toggle (left of label) ───────────────────────────────────────── const SToggle = ({label, value, onChange}) => ( ); // ── Check row (green circle with checkmark on the left) ──────────────── const SCheck = ({label, help, value, onChange}) => ( ); // ── Ja/Nej-segment (tydligt svar på ja/nej-frågor) ──────────────────── const SYesNo = ({label, help, value, onChange, yes='Ja', no='Nej'}) => (
{label} {help && {help}}
); // ── 12-month picker, value är KALENDERMÅNADER 1..12 (backend 31/8: chipsen // sparade index 0..11, vilket försköt säsongsgränserna en månad) ──────── const SMonthPicker = ({value=[], onChange}) => { const names = ['Jan','Feb','Mar','Apr','Maj','Jun','Jul','Aug','Sep','Okt','Nov','Dec']; const set = new Set(value || []); const toggle = (m) => { if (set.has(m)) set.delete(m); else set.add(m); onChange([...set].sort((a,b)=>a-b)); }; return (
{names.map((m,i)=>( ))}
); }; // ── Read-only field with copy-to-clipboard button ────────────────────── const SCopyField = ({label, value, help}) => { const [copied, setCopied] = React.useState(false); const onCopy = async () => { try { await navigator.clipboard.writeText(value || ''); setCopied(true); setTimeout(()=>setCopied(false), 1500); } catch(e){ /* ignore */ } }; return (
{label && }
{help &&
{help}
}
); }; /* SNav — kategorinav för inställningssidan (2026-06-11, flik-läge). Kontrollerad komponent: föräldern äger aktiv kategori och visar BARA den kategorins kort (egen "sida" per kategori — användarönskemål, ersätter scrollspy-långrullen). Desktop (≥1160px): vertikal rail till vänster. Smalare: horisontell chip-rad under topbaren. OBS (bevarat lärdomsmönster, se DESIGN_STATE 7h): rAF och scrollTo({behavior:'smooth'}) är opålitliga i inbäddade vyer — undvik dem för all framtida scroll-logik här. ALDRIG scrollIntoView. */ const SNav = ({ sections, active, onSelect }) => ( ); /* ── BETA-märkning (André 7/9) ─────────────────────────────────────────────── Samma två delar som på dashboarden (GcBeta/GcBetaNote i gc-core): pill vid rubriken är märkningen, notisen säger vad användaren ska göra åt den. */ const SBeta = ({ title }) => ( beta ); const SBetaNote = ({ children }) => (
Beta {children}
); Object.assign(window, { SCard, SField, SSelect, SToggle, SCheck, SYesNo, SMonthPicker, SCopyField, SNav, SBeta, SBetaNote });