réglages : curseur du seuil, onglets créés affichés en direct

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
IliesAllaliandClaude Opus 5.5 committed 2026-11-07 00:45:15 +01:00
1 parent 61d9bcc328
commit b89ceb4e00
4 files changed
+30 -14

No files matched your search

+1 -1
View File
@@ -586,7 +586,7 @@ function App() {
const setFamilyTabs = useCallback((on) => { setFamilyTabsState(on); try { localStorage.setItem('parenthese.familyTabs', on ? 'on' : 'off') } catch { /* navigateur sans stockage */ } }, [])
// Nom de famille sous le prénom dans l'arbre, celui de naissance s'il est connu (#168). Réglage de ce navigateur.
// Onglets par nom : nombre de porteurs d'un nom à partir duquel il a son onglet (8 par défaut, Patch, 07/11)
const [tabMinCount, setTabMinCountState] = useState(() => { try { const v = Number(localStorage.getItem('parenthese.tabMinCount')); return v >= 2 && v <= 99 ? v : 8 } catch { return 8 } })
const [tabMinCount, setTabMinCountState] = useState(() => { try { const v = Number(localStorage.getItem('parenthese.tabMinCount')); return v >= 2 && v <= 30 ? v : 8 } catch { return 8 } })
const setTabMinCount = useCallback((n) => { setTabMinCountState(n); try { localStorage.setItem('parenthese.tabMinCount', String(n)) } catch { /* navigateur sans stockage */ } }, [])
const [fullNames, setFullNamesState] = useState(() => { try { return localStorage.getItem('parenthese.fullNames') === 'on' } catch { return false } })
const setFullNames = useCallback((on) => { setFullNamesState(on); try { localStorage.setItem('parenthese.fullNames', on ? 'on' : 'off') } catch { /* navigateur sans stockage */ } }, [])
+23 -9
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Download, ImagePlus, Trash2, Unplug, X } from 'lucide-react'
import './SettingsPanels.css'
import {
@@ -25,6 +25,7 @@ import { t } from '../i18n/index.js'
import { readGedcomFile } from '../utils/gedcomText.js'
import { readGedcomBundle, zipErrorMessage } from '../utils/gedcomBundle.js'
import Skeleton from './Skeleton.jsx'
import { nameTabsOf } from './galaxy/nameTabs.js'
function normalizeSlug(value) {
return String(value || '')
@@ -62,6 +63,16 @@ const AdminPanel = ({
onOpenPhotoStart,
}) => {
const [section, setSection] = useState('tree')
// Seuil des onglets : le curseur montre tout de suite les onglets qu'il crée ; l'arbre ne se recalcule qu'une
// fois le curseur posé
const [tabDraft, setTabDraft] = useState(tabMinCount)
useEffect(() => { setTabDraft(tabMinCount) }, [tabMinCount])
useEffect(() => {
if (tabDraft === tabMinCount) return undefined
const timer = setTimeout(() => onTabMinCountChange?.(tabDraft), 350)
return () => clearTimeout(timer)
}, [tabDraft, tabMinCount, onTabMinCountChange])
const tabPreview = useMemo(() => (visible && familyTabs ? nameTabsOf(null, tabDraft).slice(1) : []), [visible, familyTabs, tabDraft])
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
@@ -552,18 +563,21 @@ const AdminPanel = ({
</label>
{familyTabs && (
<div className="ts-min-count">
<label htmlFor="settingsTabMinCount">{t('À partir de')}</label>
<label htmlFor="settingsTabMinCount">{t('À partir de {n} personnes au même nom', { n: tabDraft })}</label>
<input
id="settingsTabMinCount"
className="pz-input"
type="number"
inputMode="numeric"
type="range"
min={2}
max={99}
value={tabMinCount}
onChange={(event) => { const n = Math.round(Number(event.target.value)); if (n >= 2 && n <= 99) onTabMinCountChange?.(n) }}
max={30}
step={1}
value={tabDraft}
onChange={(event) => setTabDraft(Number(event.target.value))}
/>
<span>{t('personnes au même nom')}</span>
<p className="ts-tab-preview" aria-live="polite">
{tabPreview.length
? tabPreview.map((tb) => <span key={tb.key} className="pz-tag">{tb.label} <small>{tb.count}</small></span>)
: t("Aucun onglet à ce seuil : tout l'arbre sur une seule page.")}
</p>
</div>
)}
<label className={`ts-choice ${familyTabs ? '' : 'is-active'}`}>
+4 -2
View File
@@ -305,5 +305,7 @@
}
.tm-share-btn { justify-self: start; }
.ts-min-count { display: flex; flex-wrap: wrap; align-items: center; white-space: nowrap; gap: 10px; margin: 0 0 10px 16px; font: 400 14px var(--pz-sans); color: var(--pz-ink-2); }
.ts-min-count .pz-input { width: 72px; padding-block: 8px; text-align: center; font-variant-numeric: tabular-nums; }
.ts-min-count { display: grid; gap: 8px; margin: 0 0 12px 16px; font: 400 14px var(--pz-sans); color: var(--pz-ink-2); }
.ts-min-count input[type=range] { width: 100%; max-width: 320px; accent-color: var(--pz-a500); }
.ts-tab-preview { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; color: var(--pz-faint); }
.ts-tab-preview small { font-variant-numeric: tabular-nums; opacity: 0.7; }
+2 -2
View File
@@ -8,8 +8,8 @@ export default {
'Réglages de ce navigateur.': 'Saved in this browser.',
'Un onglet par nom de famille': 'One tab per last name',
"Chaque nom porté par assez de personnes reliées entre elles a son onglet, avec les conjoints. Le premier onglet montre tout l'arbre.": 'Each last name carried by enough related people gets its own tab, with their spouses. The first tab shows the whole tree.',
'À partir de': 'From',
'personnes au même nom': 'people with the same name',
'À partir de {n} personnes au même nom': 'From {n} people with the same name',
"Aucun onglet à ce seuil : tout l'arbre sur une seule page.": 'No tab at this threshold: the whole tree on one page.',
"Pas d'onglets": 'No tabs',
"Tout l'arbre sur une seule page.": 'The whole tree on one page.',
"Tout l'arbre": 'Whole tree',