mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
Final cleanup of references missed in previous rename commits: - Dockerfiles: PUBLIC_MANA_CORE_AUTH_URL → PUBLIC_MANA_AUTH_URL - Go modules: github.com/manacore/* → github.com/mana/* (7 go.mod files) - launchd plists: com.manacore.* → com.mana.* (14 files renamed + content) - Image assets: *_Manacore_AI_Credits* → *_Mana_AI_Credits* (11 files) - .env.example files: ManaCore brand strings → Mana - .prettierignore: stale apps/manacore/* paths → apps/mana/* - Markdown docs (CLAUDE.md, /docs/*): mana-core-auth → mana-auth, etc. Excluded from rename: .claude/, devlog/, manascore/ (historical content), client testimonials, blueprints, npm package refs (@mana-core/*). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
383 lines
12 KiB
Markdown
383 lines
12 KiB
Markdown
# Central Command Bar
|
|
|
|
Die zentrale Command Bar bietet eine einheitliche Schnellsuche und Navigation über alle Mana-Apps hinweg. Sie wird mit `Cmd/Ctrl+K` aktiviert und bietet Suche, Quick Actions und Tastatur-Navigation.
|
|
|
|
## Architektur
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ @mana/shared-ui │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ CommandBar.svelte │ │
|
|
│ │ - Suche mit Debounce (150ms) │ │
|
|
│ │ - Quick Actions │ │
|
|
│ │ - Tastatur-Navigation │ │
|
|
│ │ - Ergebnis-Anzeige mit Avataren │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌────────────────────┼────────────────────┐
|
|
│ │ │
|
|
┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐
|
|
│ Todo │ │ Calendar │ │ Contacts │
|
|
│ │ │ │ │ │
|
|
│ Sucht │ │ Sucht │ │ Sucht │
|
|
│ Tasks │ │ Events │ │ Kontakte │
|
|
└─────────┘ └──────────┘ └──────────┘
|
|
```
|
|
|
|
## Package
|
|
|
|
| Package | Beschreibung |
|
|
|---------|--------------|
|
|
| `@mana/shared-ui` | CommandBar Svelte-Komponente + TypeScript-Typen |
|
|
|
|
## Keyboard Shortcut
|
|
|
|
| Shortcut | Aktion |
|
|
|----------|--------|
|
|
| `Cmd/Ctrl+K` | Command Bar öffnen |
|
|
| `↑↓` | Navigation durch Ergebnisse |
|
|
| `Enter` | Auswahl bestätigen |
|
|
| `Escape` | Schließen |
|
|
|
|
## TypeScript Interfaces
|
|
|
|
### CommandBarItem
|
|
|
|
Ein Suchergebnis:
|
|
|
|
```typescript
|
|
interface CommandBarItem {
|
|
id: string; // Eindeutige ID
|
|
title: string; // Haupttext (z.B. Name, Titel)
|
|
subtitle?: string; // Untertitel (z.B. Datum, E-Mail)
|
|
icon?: string; // Icon-Name
|
|
imageUrl?: string; // Avatar/Bild URL
|
|
isFavorite?: boolean; // Favorit-Markierung (zeigt Herz-Icon)
|
|
}
|
|
```
|
|
|
|
### QuickAction
|
|
|
|
Eine Schnellaktion (ohne Suche):
|
|
|
|
```typescript
|
|
interface QuickAction {
|
|
id: string; // Eindeutige ID
|
|
label: string; // Anzeigetext
|
|
icon: string; // Icon-Name
|
|
href?: string; // Link-Ziel (optional)
|
|
shortcut?: string; // Tastenkürzel-Anzeige
|
|
onclick?: () => void; // Click-Handler (alternativ zu href)
|
|
}
|
|
```
|
|
|
|
### Unterstützte Icons
|
|
|
|
Die CommandBar unterstützt folgende eingebaute Icons:
|
|
|
|
| Icon-Name | Beschreibung |
|
|
|-----------|--------------|
|
|
| `plus` | Plus-Zeichen (Neu erstellen) |
|
|
| `heart` | Herz (Favoriten) |
|
|
| `tag` | Tag/Label |
|
|
| `upload` | Upload (Import) |
|
|
| `calendar` | Kalender |
|
|
| `clock` | Uhr |
|
|
| `check` | Häkchen |
|
|
| `settings` | Zahnrad (Einstellungen) |
|
|
| `list` | Liste |
|
|
|
|
## Komponenten-Props
|
|
|
|
```typescript
|
|
interface Props {
|
|
open: boolean; // Sichtbarkeit
|
|
onClose: () => void; // Schließen-Handler
|
|
onSearch: (query: string) => Promise<CommandBarItem[]>; // Such-Funktion
|
|
onSelect: (item: CommandBarItem) => void; // Auswahl-Handler
|
|
quickActions?: QuickAction[]; // Schnellaktionen
|
|
placeholder?: string; // Suchfeld-Placeholder
|
|
emptyText?: string; // Text bei leeren Ergebnissen
|
|
searchingText?: string; // Text während Suche
|
|
}
|
|
```
|
|
|
|
## Nutzung
|
|
|
|
### Basis-Beispiel
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { CommandBar } from '@mana/shared-ui';
|
|
import type { CommandBarItem, QuickAction } from '@mana/shared-ui';
|
|
|
|
let commandBarOpen = $state(false);
|
|
|
|
// Quick Actions definieren
|
|
const quickActions: QuickAction[] = [
|
|
{ id: 'new', label: 'Neu erstellen', icon: 'plus', href: '/new', shortcut: 'N' },
|
|
{ id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' },
|
|
];
|
|
|
|
// Such-Funktion implementieren
|
|
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
|
const results = await api.search(query);
|
|
return results.map((item) => ({
|
|
id: item.id,
|
|
title: item.name,
|
|
subtitle: item.description,
|
|
}));
|
|
}
|
|
|
|
// Auswahl verarbeiten
|
|
function handleSelect(item: CommandBarItem) {
|
|
goto(`/item/${item.id}`);
|
|
}
|
|
|
|
// Keyboard Shortcut registrieren
|
|
function handleKeydown(event: KeyboardEvent) {
|
|
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
|
event.preventDefault();
|
|
commandBarOpen = true;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onkeydown={handleKeydown} />
|
|
|
|
<CommandBar
|
|
bind:open={commandBarOpen}
|
|
onClose={() => (commandBarOpen = false)}
|
|
onSearch={handleSearch}
|
|
onSelect={handleSelect}
|
|
{quickActions}
|
|
placeholder="Suchen..."
|
|
emptyText="Keine Ergebnisse"
|
|
searchingText="Suche..."
|
|
/>
|
|
```
|
|
|
|
## App-spezifische Implementierungen
|
|
|
|
### Todo App
|
|
|
|
```typescript
|
|
// Quick Actions
|
|
const quickActions: QuickAction[] = [
|
|
{ id: 'new', label: 'Neue Aufgabe erstellen', icon: 'plus', href: '/task/new', shortcut: 'N' },
|
|
{ id: 'kanban', label: 'Kanban-Board', icon: 'list', href: '/kanban' },
|
|
{ id: 'stats', label: 'Statistiken', icon: 'chart', href: '/statistics' },
|
|
{ id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' },
|
|
];
|
|
|
|
// Suche: Tasks durchsuchen
|
|
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
|
const tasks = await getTasks({ search: query });
|
|
return tasks.slice(0, 10).map((task) => ({
|
|
id: task.id,
|
|
title: task.title,
|
|
subtitle: task.isCompleted
|
|
? '✓ Erledigt'
|
|
: task.dueDate
|
|
? new Date(task.dueDate).toLocaleDateString('de-DE')
|
|
: 'Kein Datum',
|
|
}));
|
|
}
|
|
|
|
// Auswahl: Zu Task navigieren
|
|
function handleSelect(item: CommandBarItem) {
|
|
goto(`/task/${item.id}`);
|
|
}
|
|
```
|
|
|
|
### Calendar App
|
|
|
|
```typescript
|
|
// Quick Actions
|
|
const quickActions: QuickAction[] = [
|
|
{ id: 'new', label: 'Neuen Termin erstellen', icon: 'plus', href: '/event/new', shortcut: 'N' },
|
|
{ id: 'today', label: 'Zu Heute springen', icon: 'calendar', onclick: () => viewStore.goToToday() },
|
|
{ id: 'agenda', label: 'Agenda anzeigen', icon: 'list', href: '/agenda' },
|
|
{ id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' },
|
|
];
|
|
|
|
// Suche: Events durchsuchen
|
|
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
|
const result = await searchEvents(query);
|
|
if (result.error || !result.data) return [];
|
|
|
|
return result.data.slice(0, 10).map((event) => ({
|
|
id: event.id,
|
|
title: event.title,
|
|
subtitle: format(new Date(event.startTime), 'dd. MMM yyyy, HH:mm', { locale: de }),
|
|
}));
|
|
}
|
|
|
|
// Auswahl: Zu Event navigieren
|
|
function handleSelect(item: CommandBarItem) {
|
|
goto(`/event/${item.id}`);
|
|
}
|
|
```
|
|
|
|
### Contacts App
|
|
|
|
```typescript
|
|
// Quick Actions
|
|
const quickActions: QuickAction[] = [
|
|
{ id: 'new', label: 'Neuen Kontakt erstellen', icon: 'plus', href: '/contacts/new', shortcut: 'N' },
|
|
{ id: 'favorites', label: 'Favoriten anzeigen', icon: 'heart', href: '/favorites' },
|
|
{ id: 'tags', label: 'Tags verwalten', icon: 'tag', href: '/tags' },
|
|
{ id: 'import', label: 'Kontakte importieren', icon: 'upload', href: '/data?tab=import' },
|
|
];
|
|
|
|
// Suche: Kontakte durchsuchen
|
|
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
|
const response = await contactsApi.list({ search: query, limit: 10 });
|
|
return (response.contacts || []).map((contact) => ({
|
|
id: contact.id,
|
|
title: contact.displayName ||
|
|
[contact.firstName, contact.lastName].filter(Boolean).join(' ') ||
|
|
contact.email || 'Unbekannt',
|
|
subtitle: contact.company || contact.email,
|
|
imageUrl: contact.photoUrl,
|
|
isFavorite: contact.isFavorite,
|
|
}));
|
|
}
|
|
|
|
// Auswahl: Kontakt-Modal öffnen
|
|
function handleSelect(item: CommandBarItem) {
|
|
goto(`/contacts/${item.id}`);
|
|
}
|
|
```
|
|
|
|
## Funktionen
|
|
|
|
### Suche
|
|
|
|
- **Debounce:** 150ms Verzögerung für Performance
|
|
- **Loading-State:** Spinner während der Suche
|
|
- **Empty State:** Konfigurierbare Meldung bei keinen Ergebnissen
|
|
- **Limit:** Ergebnisse werden typischerweise auf 10 begrenzt
|
|
|
|
### Quick Actions
|
|
|
|
Wenn kein Suchtext eingegeben ist, werden Quick Actions angezeigt:
|
|
|
|
- Navigation mit Pfeiltasten
|
|
- Ausführung mit Enter
|
|
- Keyboard-Shortcuts werden rechts angezeigt
|
|
|
|
### Ergebnis-Anzeige
|
|
|
|
- **Avatar:** Bild oder Initialen
|
|
- **Titel:** Haupttext
|
|
- **Untertitel:** Zusatzinfo (grau)
|
|
- **Favorit:** Herz-Icon wenn `isFavorite: true`
|
|
- **Hover:** Visuelles Feedback bei Maus-Over
|
|
|
|
### Keyboard Navigation
|
|
|
|
| Taste | Aktion |
|
|
|-------|--------|
|
|
| `↑` / `↓` | Durch Ergebnisse navigieren |
|
|
| `Enter` | Ausgewähltes Element öffnen |
|
|
| `Escape` | Command Bar schließen |
|
|
|
|
## Styling
|
|
|
|
Die Command Bar verwendet ein dunkles Theme mit CSS-Variablen:
|
|
|
|
```css
|
|
.command-modal {
|
|
background: #1a1a1a;
|
|
border: 1px solid #333;
|
|
border-radius: 12px;
|
|
max-width: 560px;
|
|
}
|
|
|
|
.command-result.selected {
|
|
background: #2a2a2a;
|
|
}
|
|
|
|
.result-avatar {
|
|
background: #3b82f6; /* Primary Color */
|
|
}
|
|
|
|
.result-favorite {
|
|
color: #ef4444; /* Rot für Herz */
|
|
}
|
|
```
|
|
|
|
### Animationen
|
|
|
|
- **Fade In:** Backdrop erscheint mit 0.15s
|
|
- **Slide In:** Modal gleitet von oben mit 0.2s
|
|
- **Loading Spinner:** Rotation Animation
|
|
|
|
## Integration in Layout
|
|
|
|
Typischerweise wird die CommandBar im App-Layout integriert:
|
|
|
|
```svelte
|
|
<!-- src/routes/(app)/+layout.svelte -->
|
|
<script lang="ts">
|
|
import { CommandBar } from '@mana/shared-ui';
|
|
|
|
let commandBarOpen = $state(false);
|
|
|
|
function handleKeydown(event: KeyboardEvent) {
|
|
// Cmd/Ctrl+K funktioniert auch in Input-Feldern
|
|
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
|
event.preventDefault();
|
|
commandBarOpen = true;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onkeydown={handleKeydown} />
|
|
|
|
<!-- Haupt-Layout -->
|
|
<PillNavigation ... />
|
|
|
|
<main>
|
|
{@render children()}
|
|
</main>
|
|
|
|
<!-- Command Bar (global) -->
|
|
<CommandBar
|
|
bind:open={commandBarOpen}
|
|
onClose={() => (commandBarOpen = false)}
|
|
onSearch={handleSearch}
|
|
onSelect={handleSelect}
|
|
{quickActions}
|
|
/>
|
|
```
|
|
|
|
## Dateien
|
|
|
|
### @mana/shared-ui
|
|
|
|
| Datei | Beschreibung |
|
|
|-------|--------------|
|
|
| `src/command-bar/CommandBar.svelte` | Hauptkomponente |
|
|
| `src/command-bar/index.ts` | Exports |
|
|
| `src/index.ts` | Package-Export |
|
|
|
|
## Vorteile
|
|
|
|
- **Einheitliche UX:** Gleiche Interaktion in allen Apps
|
|
- **Schnelle Navigation:** Sofortiger Zugriff auf häufige Aktionen
|
|
- **Tastatur-fokussiert:** Optimiert für Power-User
|
|
- **Flexibel:** App-spezifische Such-Logik und Quick Actions
|
|
- **Responsive:** Funktioniert auf Desktop und Mobile
|
|
|
|
## Best Practices
|
|
|
|
1. **Such-Performance:** Limit auf 10 Ergebnisse setzen
|
|
2. **Quick Actions:** Maximal 4-5 Aktionen für Übersichtlichkeit
|
|
3. **Sinnvolle Shortcuts:** Mnemonische Kürzel (N=Neu, S=Settings)
|
|
4. **Gute Subtitles:** Zusätzliche Info für eindeutige Identifikation
|
|
5. **Keyboard First:** Cmd+K sollte immer funktionieren, auch in Input-Feldern
|