Kundenansicht neu P1: Etage 2 mit Zurück, Reiterzeile und Brotkrume
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a51643c4a5
commit
748226d631
3 files changed
+228
No files matched your search
@@ -0,0 +1,117 @@
|
||||
/* Kundenansicht neu: Etage 2. Eine Unterseite (Funktion, Detailseite, Passwort …) schiebt sich als Blatt über die Hauptseite.
|
||||
Handy: Die Zeile unten fährt eine Etage hoch und zeigt „Zurück“ und, falls die Seite Reiter meldet, deren Kurzlabels als Balken.
|
||||
Desktop: Brotkrume statt Zeile, Reiter als Unterstrich im Slot unter der Überschrift.
|
||||
Der Inhalt meldet Reiter und Titel ohne Import per CustomEvent (bubbles): kn:reiter { eintraege, aktiv, bei } (leere Liste = keine Reiter)
|
||||
und kn:titel { text }. Alles Sichtbare wird mit h() und textContent gebaut. */
|
||||
|
||||
import { h, leeren, text } from './dom.js'
|
||||
import { baueReiter } from './reiter.js'
|
||||
|
||||
const gleicheEintraege = (a, b) => a.length === b.length && a.every((x, i) => x.key === b[i].key && x.label === b[i].label && (x.kurz ?? '') === (b[i].kurz ?? ''))
|
||||
|
||||
export function baueEtage({ blatt, haupt, zeile, titel, brot, reiterSlot, istHandy, onZurueck }) {
|
||||
const stand = { offen: false, titel: '', eltern: null, seite: '', reiter: null, chatPunkt: false, reiterEl: null, zurueckEl: null }
|
||||
|
||||
const zurueckLabel = () => `Zurück zu ${stand.eltern.label}${stand.chatPunkt ? ', neue Nachricht im Chat' : ''}`
|
||||
|
||||
function baueZurueck(voll) {
|
||||
return h('button', { class: voll ? 'kn-zurueck kn-zurueck--voll' : 'kn-zurueck', type: 'button', 'aria-label': zurueckLabel(), on: { click: () => onZurueck() } },
|
||||
h('span', { class: 'kn-l' }, 'Zurück', stand.chatPunkt ? h('span', { class: 'kn-punkt' }) : null))
|
||||
}
|
||||
|
||||
function neuerReiter(art) {
|
||||
const { eintraege, aktiv } = stand.reiter
|
||||
// Handy zeigt die Kurzlabels (der Balken hat wenig Platz), Desktop die vollen Beschriftungen
|
||||
const zeigen = art === 'balken' ? eintraege : eintraege.map(({ kurz, ...rest }) => rest)
|
||||
return baueReiter({ eintraege: zeigen, aktiv, art, ariaLabel: 'Reiter', bei: (key) => stand.reiter?.bei?.(key) })
|
||||
}
|
||||
|
||||
function zeichneBrot() {
|
||||
leeren(brot)
|
||||
if (!stand.offen) return
|
||||
const verlassen = (ev) => {
|
||||
if (ev.defaultPrevented || ev.button > 0 || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return
|
||||
ev.preventDefault() // die Brotkrume führt wie „Zurück“ zur Elternseite, ohne neuen Verlaufseintrag
|
||||
onZurueck()
|
||||
}
|
||||
brot.append(h('a', { href: stand.eltern.hash, on: { click: verlassen } }, stand.eltern.label), h('span', { class: 'kn-brot-trenner', 'aria-hidden': 'true' }, ' / '), h('span', { 'aria-current': 'page' }, stand.titel))
|
||||
}
|
||||
|
||||
// Zeile (Handy) und Reiter-Slot (Desktop) aus dem Stand neu aufbauen
|
||||
function zeichne() {
|
||||
leeren(zeile); leeren(reiterSlot)
|
||||
stand.zurueckEl = null; stand.reiterEl = null
|
||||
zeichneBrot()
|
||||
if (!stand.offen) return
|
||||
if (istHandy()) {
|
||||
stand.zurueckEl = baueZurueck(!stand.reiter)
|
||||
zeile.append(stand.zurueckEl)
|
||||
if (stand.reiter) {
|
||||
stand.reiterEl = neuerReiter('balken')
|
||||
zeile.append(h('span', { class: 'kn-vl' }), stand.reiterEl)
|
||||
}
|
||||
} else if (stand.reiter) {
|
||||
stand.reiterEl = neuerReiter('unterstrich')
|
||||
reiterSlot.append(stand.reiterEl)
|
||||
}
|
||||
stand.reiterEl?.messen()
|
||||
}
|
||||
|
||||
function oeffne({ titel: titelText, eltern, seite }) {
|
||||
const warOffen = stand.offen
|
||||
Object.assign(stand, { offen: true, titel: titelText, eltern, seite, reiter: null })
|
||||
text(titel, titelText)
|
||||
for (const s of blatt.querySelectorAll('section[data-etage]')) s.hidden = s.getAttribute('data-etage') !== seite
|
||||
haupt.setAttribute('inert', '')
|
||||
if (!warOffen) {
|
||||
blatt.hidden = false
|
||||
void blatt.offsetWidth // Ausgangslage festschreiben, damit das Blatt von unten einfährt
|
||||
blatt.classList.add('kn-blatt--auf')
|
||||
}
|
||||
zeichne()
|
||||
}
|
||||
|
||||
function schliesse() {
|
||||
stand.offen = false
|
||||
stand.reiter = null
|
||||
blatt.classList.remove('kn-blatt--auf')
|
||||
blatt.hidden = true
|
||||
haupt.removeAttribute('inert')
|
||||
zeichne()
|
||||
}
|
||||
|
||||
function setzeReiter({ eintraege = [], aktiv = '', bei } = {}) {
|
||||
if (!stand.offen) return
|
||||
const alt = stand.reiter
|
||||
stand.reiter = eintraege.length ? { eintraege, aktiv, bei } : null
|
||||
// Gleiche Reiter bleiben stehen (der Balken fährt weiter), nur die Auswahl wechselt
|
||||
if (alt && stand.reiter && stand.reiterEl && gleicheEintraege(alt.eintraege, eintraege)) stand.reiterEl.setzeAktiv(aktiv)
|
||||
else zeichne()
|
||||
}
|
||||
|
||||
function setzeChatPunkt(an) {
|
||||
stand.chatPunkt = Boolean(an)
|
||||
const knopf = stand.zurueckEl
|
||||
if (!knopf) return
|
||||
const wort = knopf.querySelector('.kn-l')
|
||||
const punkt = wort.querySelector('.kn-punkt')
|
||||
if (stand.chatPunkt && !punkt) wort.append(h('span', { class: 'kn-punkt' }))
|
||||
else if (!stand.chatPunkt && punkt) punkt.remove()
|
||||
knopf.setAttribute('aria-label', zurueckLabel())
|
||||
}
|
||||
|
||||
// Meldungen aus dem Inhalt; Meldungen einer verborgenen Seite oder nach dem Schließen gelten nicht
|
||||
const vonDieserSeite = (ev) => {
|
||||
const s = ev.target?.closest?.('section[data-etage]')
|
||||
return stand.offen && (!s || s.getAttribute('data-etage') === stand.seite)
|
||||
}
|
||||
blatt.addEventListener('kn:reiter', (ev) => { if (vonDieserSeite(ev)) setzeReiter(ev.detail ?? {}) })
|
||||
blatt.addEventListener('kn:titel', (ev) => {
|
||||
if (!vonDieserSeite(ev) || !ev.detail?.text) return
|
||||
stand.titel = String(ev.detail.text)
|
||||
text(titel, stand.titel)
|
||||
zeichneBrot()
|
||||
})
|
||||
|
||||
return { oeffne, schliesse, setzeReiter, neuZeichnen: zeichne, setzeChatPunkt, istOffen: () => stand.offen }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Kundenansicht neu: Hülle (Menü, Kopf, Blatt, Leiste, Streifen, Konto). Weiß, Haarlinie, Schwarz; keine Verläufe, kein Glas.
|
||||
Die Grundregeln gelten am Desktop, die Handy-Abfrage überschreibt für das Handy. */
|
||||
|
||||
/* ---------- Blatt (Etage 2) ---------- */
|
||||
|
||||
.kn-blatt { max-width: 1160px; padding: 36px 48px 80px; background: var(--kn-bg); }
|
||||
.kn-haupt[inert] { display: none; } /* Desktop: die Unterseite ersetzt die Hauptseite in der Spalte */
|
||||
|
||||
.kn-brot { margin-bottom: 8px; font-size: 13px; line-height: 20px; color: var(--kn-text-2); }
|
||||
.kn-brot a { color: var(--kn-text-2); }
|
||||
.kn-brot-trenner { color: var(--kn-text-3); }
|
||||
.kn-brot [aria-current="page"] { color: var(--kn-text); }
|
||||
|
||||
.kn-zurueck { position: relative; display: flex; align-items: center; gap: 6px; height: 44px; padding: 0 8px 0 6px; font-size: 15px; font-weight: 600; color: var(--kn-text); }
|
||||
.kn-zurueck::before { content: ""; width: 9px; height: 9px; margin-left: 4px; border: solid; border-width: 0 0 2px 2px; transform: rotate(45deg); }
|
||||
.kn-zurueck--voll { flex: 1; }
|
||||
.kn-zurueck .kn-l { position: relative; line-height: 20px; }
|
||||
.kn-vl { flex: none; width: 1px; height: 24px; margin: 0 4px; background: var(--kn-line); }
|
||||
.kn-punkt { position: absolute; top: -2px; left: 100%; width: 9px; height: 9px; margin-left: 2px; border-radius: 50%; background: #b3093c; box-shadow: 0 0 0 2px var(--kn-bg); }
|
||||
|
||||
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
|
||||
.kn { --kn-unten: calc(var(--kn-zeile-h) + env(safe-area-inset-bottom)); }
|
||||
.kn-blatt {
|
||||
position: fixed; inset: 0 0 var(--kn-unten, var(--kn-zeile-h)); z-index: 20; max-width: none; padding: 0 16px 24px;
|
||||
overflow-y: auto; overscroll-behavior: contain; border-radius: 12px 12px 0 0;
|
||||
box-shadow: 0 0 0 1px rgba(26,31,54,.06), 0 -8px 24px rgba(26,31,54,.1);
|
||||
transform: translateY(100%); transition: transform 550ms var(--kn-bremse);
|
||||
}
|
||||
.kn-blatt--auf { transform: none; }
|
||||
.kn-haupt[inert] { display: block; } /* am Handy bleibt die Hauptseite unter dem Blatt stehen */
|
||||
.kn-brot { display: none; }
|
||||
}
|
||||
Reference in new issue
Block a user