AppWindowHeader

ui layoutheaderapp-window

Kopfzeile des App-Fensters im Raster auto·1fr·auto: Marke links, Suchschlitz mittig, Status und Konto rechts. Die Slots sind offen — Logo, Badges und Profilmenü sind Inhalte (SearchInput, LicenseBadge, AccountMenu), nicht Aufgabe des Fensters. Mit stackSearchWhenNarrow rutscht die Suche unter 1180px in eine eigene Zeile (opt-in).

ekklesi:demo
Pro

Abhängigkeiten

Barrierefreiheit

  • Bringt selbst keine Landmark mit — sie kommt vom <header> des AppWindow. Bei eigenständiger Verwendung selbst eine setzen.

Beispiele

Vollständige Kopfzeile
<AppWindowHeader
  brand={<Brand />}
  search={<SearchInput value={q} onChange={setQ} />}
  status={<><CtBadge /><LicenseBadge plan={plan} /></>}
  account={<AccountMenu … />}
/>
stackSearchWhenNarrow — nur bei voller Kopfzeile
// OPT-IN: der Umbruch hängt am INHALT, nicht an der Fensterbreite.
// presenter trägt drei Pillen neben einer 480px-Suche und braucht ihn:
<AppWindowHeader stackSearchWhenNarrow brand={…} search={…} status={…} account={…} />

// worship hat nur eine Badge — ohne die Prop bleibt die Kopfzeile
// einzeilig (57px statt 109px):
<AppWindowHeader brand={…} search={…} status={<QuellenBadge />} account={…} />

Genutzte Tokens · 4

--app-window-gutter--app-window-header-height--space-3--space-5

Quelldateien

components/ui/AppWindowHeader.tsx tsx
import type { ReactNode } from 'react';
import styles from './AppWindowHeader.module.css';

/**
 * Kopfzeile des App-Fensters: Marke links, Suchschlitz mittig, Status und
 * Konto rechts.
 *
 * Raster `auto 1fr auto` nach dem Vorbild aus worship (`WorshipHeader`) — der
 * presenter nutzte bisher `flex` mit `space-between`, wodurch die Mitte nur
 * zufällig mittig lag. Die Slots sind bewusst offen: Logo, Badges und
 * Profilmenü sind INHALTE (`AccountMenu`, `LicenseBadge`, `SearchInput`),
 * nicht Aufgabe des Fensters.
 */
export interface AppWindowHeaderProps {
  /** Links: Signet + Wortmarke + Version. */
  brand: ReactNode;
  /** Mitte: Suchschlitz. Fehlt sie, bleibt die Spur leer und schiebt den
   *  rechten Block ans Ende. */
  search?: ReactNode;
  /** Rechts vor dem Konto: CT-Badge, Lizenz-Badge, App-Extras. */
  status?: ReactNode;
  /** Ganz rechts: Profil-Icon / Konto-Menü. */
  account?: ReactNode;
  /**
   * Unter 1180px die Suche in eine eigene Zeile umbrechen (Kopfzeile wird
   * zweizeilig). OPT-IN, weil es vom Inhalt abhängt, nicht von der Breite:
   * der presenter trägt rechts drei Pillen neben einer 480px-Suche und braucht
   * den Umbruch; worship kommt mit einer Badge in einer Zeile aus und würde
   * sonst 53px Höhe verschenken.
   */
  stackSearchWhenNarrow?: boolean;
  className?: string;
}

export function AppWindowHeader({
  brand,
  search,
  status,
  account,
  stackSearchWhenNarrow = false,
  className,
}: AppWindowHeaderProps) {
  const cls = [styles.header, stackSearchWhenNarrow ? styles.stack : '', className]
    .filter(Boolean)
    .join(' ');
  return (
    <div className={cls}>
      <div className={styles.brand}>{brand}</div>
      <div className={styles.search}>{search}</div>
      <div className={styles.right}>
        {status}
        {account}
      </div>
    </div>
  );
}
components/ui/AppWindowHeader.module.css css
.header {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-5);
  min-height: var(--app-window-header-height);
  padding-inline: var(--app-window-gutter);
}

.brand {
  display: flex;
  align-items: center;
  min-width: 0;
}

.search {
  display: flex;
  justify-content: center;
  min-width: 0;
}

.right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  justify-self: end;
}

/* Schmale Fenster: die Suche rutscht in eine eigene Zeile unter Marke und
   Konto. Der presenter löste das früher über `> :nth-child(2)` — das brach,
   sobald die Slots gerastert wurden. Hier hängt es an der Klasse. */
@media (max-width: 1180px) {
  .header.stack {
    grid-template-columns: auto 1fr;
    grid-template-areas:
      'brand right'
      'search search';
    row-gap: var(--space-5);
    padding-block: var(--space-3);
  }
  .stack .brand {
    grid-area: brand;
  }
  .stack .right {
    grid-area: right;
  }
  .stack .search {
    grid-area: search;
  }
  /* Die Suche soll die volle Zeile füllen. `justify-content: stretch` wirkt im
     Flex-Container nicht — das Kind muss selbst wachsen dürfen, und sein
     `max-width` (im presenter 480px) muss weg.
     `.header .search > *` statt `.search > *`: bei gleicher Spezifität gewinnt
     die zuletzt eingebundene Datei, und die App-CSS steht nach der des
     Pakets. Zwei Klassen schlagen die eine der Konsumenten-Regel. */
  .header.stack .search > * {
    flex: 1;
    width: 100%;
    max-width: none;
  }
}

Holen

# MCP (Claude Code / Cursor)
get_component({ name: "app-window-header" })

# Registry direkt
https://www.ekklesi.tools/design/registry/components/app-window-header.json