From c712cc7995cedb43700b62099d0818e9bd302cb6 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:43:19 +0100 Subject: [PATCH] feat(shared-ui): add reusable HelpModal system with keyboard shortcuts and syntax panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create new help module with HelpModal, KeyboardShortcutsPanel, and SyntaxHelpPanel components - Add common shortcuts and syntax constants for reusability - Refactor InputBarHelpModal to use new HelpModal component - Fix ContextMenu event handling with stopPropagation and pointer-events - Fix Modal z-index for proper stacking context - Add CalendarHeaderContextMenu import to WeekView 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../lib/components/calendar/WeekView.svelte | 1 + .../apps/web/src/lib/config/helpConfig.ts | 108 +++++ .../src/context-menu/ContextMenu.svelte | 16 +- packages/shared-ui/src/help/HelpModal.svelte | 183 ++++++++ .../src/help/KeyboardShortcutsPanel.svelte | 251 +++++++++++ .../shared-ui/src/help/SyntaxHelpPanel.svelte | 415 ++++++++++++++++++ packages/shared-ui/src/help/constants.ts | 122 +++++ packages/shared-ui/src/help/index.ts | 18 + packages/shared-ui/src/help/types.ts | 93 ++++ packages/shared-ui/src/index.ts | 19 + packages/shared-ui/src/organisms/Modal.svelte | 3 +- .../src/quick-input/InputBarHelpModal.svelte | 204 +-------- 12 files changed, 1238 insertions(+), 195 deletions(-) create mode 100644 apps/calendar/apps/web/src/lib/config/helpConfig.ts create mode 100644 packages/shared-ui/src/help/HelpModal.svelte create mode 100644 packages/shared-ui/src/help/KeyboardShortcutsPanel.svelte create mode 100644 packages/shared-ui/src/help/SyntaxHelpPanel.svelte create mode 100644 packages/shared-ui/src/help/constants.ts create mode 100644 packages/shared-ui/src/help/index.ts create mode 100644 packages/shared-ui/src/help/types.ts diff --git a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte index ceb0d9d0a..b92335d4a 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte @@ -20,6 +20,7 @@ import EventCard from './EventCard.svelte'; import TaskBlock from './TaskBlock.svelte'; import EventContextMenu from '$lib/components/event/EventContextMenu.svelte'; + import CalendarHeaderContextMenu from './CalendarHeaderContextMenu.svelte'; import { goto } from '$app/navigation'; import { format, diff --git a/apps/calendar/apps/web/src/lib/config/helpConfig.ts b/apps/calendar/apps/web/src/lib/config/helpConfig.ts new file mode 100644 index 000000000..047f59619 --- /dev/null +++ b/apps/calendar/apps/web/src/lib/config/helpConfig.ts @@ -0,0 +1,108 @@ +import { NavigationArrow, CalendarBlank, ListChecks } from '@manacore/shared-icons'; +import { + COMMON_SHORTCUTS, + COMMON_SYNTAX, + DEFAULT_LIVE_EXAMPLE, + type HelpModalConfig, + type ShortcutCategory, + type SyntaxGroup, +} from '@manacore/shared-ui'; + +/** + * Calendar-specific keyboard shortcuts + */ +const CALENDAR_SHORTCUTS: ShortcutCategory[] = [ + { + id: 'navigation', + title: 'Navigation', + icon: NavigationArrow, + shortcuts: [ + { + keys: ['Cmd', '1'], + altKeys: ['Ctrl', '1'], + description: 'Kalender öffnen', + category: 'navigation', + }, + { + keys: ['Cmd', '2'], + altKeys: ['Ctrl', '2'], + description: 'Aufgaben öffnen', + category: 'navigation', + }, + { + keys: ['Cmd', '3'], + altKeys: ['Ctrl', '3'], + description: 'Statistiken öffnen', + category: 'navigation', + }, + { + keys: ['Cmd', '4'], + altKeys: ['Ctrl', '4'], + description: 'Einstellungen öffnen', + category: 'navigation', + }, + ], + }, + { + id: 'calendar', + title: 'Kalender', + icon: CalendarBlank, + shortcuts: [ + { + keys: ['Enter'], + description: 'Event/Task öffnen', + category: 'calendar', + }, + { + keys: ['Space'], + description: 'Event/Task öffnen', + category: 'calendar', + }, + { + keys: ['Esc'], + description: 'Drag/Resize abbrechen', + category: 'calendar', + }, + ], + }, + { + id: 'tasks', + title: 'Aufgaben', + icon: ListChecks, + shortcuts: [ + { + keys: ['Enter'], + description: 'Aufgabe öffnen', + category: 'tasks', + }, + { + keys: ['Space'], + description: 'Aufgabe abhaken', + category: 'tasks', + }, + ], + }, +]; + +/** + * Calendar-specific syntax patterns (extends common syntax) + */ +const CALENDAR_SYNTAX: SyntaxGroup[] = [ + // Calendar uses all common syntax patterns +]; + +/** + * Complete help configuration for the Calendar app + * Combines common shortcuts/syntax with Calendar-specific ones + */ +export const CALENDAR_HELP_CONFIG: HelpModalConfig = { + shortcuts: [...COMMON_SHORTCUTS, ...CALENDAR_SHORTCUTS], + syntax: [...COMMON_SYNTAX, ...CALENDAR_SYNTAX], + defaultTab: 'shortcuts', + liveExample: DEFAULT_LIVE_EXAMPLE, +}; + +/** + * Export individual parts for customization + */ +export { CALENDAR_SHORTCUTS, CALENDAR_SYNTAX }; diff --git a/packages/shared-ui/src/context-menu/ContextMenu.svelte b/packages/shared-ui/src/context-menu/ContextMenu.svelte index 0f7db0995..997a6a1bd 100644 --- a/packages/shared-ui/src/context-menu/ContextMenu.svelte +++ b/packages/shared-ui/src/context-menu/ContextMenu.svelte @@ -89,9 +89,19 @@
{ + e.preventDefault(); + e.stopPropagation(); + onClose(); + }} + onclick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onClose(); + }} oncontextmenu={(e) => { e.preventDefault(); + e.stopPropagation(); onClose(); }} >
@@ -149,6 +159,8 @@ z-index: 9998; /* Transparent backdrop - just blocks clicks */ background: transparent; + /* Ensure backdrop can receive events even when parent has pointer-events: none */ + pointer-events: auto; } .context-menu { @@ -160,6 +172,8 @@ background: var(--color-surface-elevated-3); border: 1px solid hsl(var(--color-border)); border-radius: var(--radius-lg); + /* Ensure menu can receive events even when parent has pointer-events: none */ + pointer-events: auto; } .menu-item { diff --git a/packages/shared-ui/src/help/HelpModal.svelte b/packages/shared-ui/src/help/HelpModal.svelte new file mode 100644 index 000000000..94b206bfb --- /dev/null +++ b/packages/shared-ui/src/help/HelpModal.svelte @@ -0,0 +1,183 @@ + + + +
+ + + + + +
+
+ + diff --git a/packages/shared-ui/src/help/KeyboardShortcutsPanel.svelte b/packages/shared-ui/src/help/KeyboardShortcutsPanel.svelte new file mode 100644 index 000000000..d28c04116 --- /dev/null +++ b/packages/shared-ui/src/help/KeyboardShortcutsPanel.svelte @@ -0,0 +1,251 @@ + + +
+ {#each categories as category} + {@const CategoryIcon = getCategoryIcon(category)} +
+
+ + {category.title} +
+ +
+ {#each category.shortcuts as shortcut} + {@const ShortcutIcon = getShortcutIcon(shortcut.keys)} +
+
+ +
+
+ {#each shortcut.keys as key, i} + {#if i > 0}+{/if} + {key} + {/each} + {#if shortcut.altKeys && !compact} + + oder + {#each shortcut.altKeys as key, i} + {#if i > 0}+{/if} + {key} + {/each} + + {/if} +
+ {shortcut.description} +
+ {/each} +
+
+ {/each} +
+ + diff --git a/packages/shared-ui/src/help/SyntaxHelpPanel.svelte b/packages/shared-ui/src/help/SyntaxHelpPanel.svelte new file mode 100644 index 000000000..f39896d5f --- /dev/null +++ b/packages/shared-ui/src/help/SyntaxHelpPanel.svelte @@ -0,0 +1,415 @@ + + +
+ {#if introText && !compact} +

{introText}

+ {/if} + + {#each groups as group} +
+

{group.title}

+
+ {#each group.items as item} + {@const Icon = item.icon ?? getPatternIcon(item.pattern)} +
+
+ +
+
+
+ {item.pattern} + {item.description} +
+
+ {#each item.examples as ex} + {#if typeof ex === 'string'} + {ex} + {:else} + + {ex.text} + {#if ex.label} + {ex.label} + {/if} + + {/if} + {/each} +
+
+
+ {/each} +
+
+ {/each} + + {#if showLiveExample && !compact} +
+
Beispiel-Eingabe
+
+ {#each example.highlights as hl} + {hl.content} + {/each} +
+
+ {/if} +
+ + diff --git a/packages/shared-ui/src/help/constants.ts b/packages/shared-ui/src/help/constants.ts new file mode 100644 index 000000000..e57e158a7 --- /dev/null +++ b/packages/shared-ui/src/help/constants.ts @@ -0,0 +1,122 @@ +import type { ShortcutCategory, SyntaxGroup } from './types'; + +/** + * Common keyboard shortcuts shared across all apps with InputBar + */ +export const COMMON_SHORTCUTS: ShortcutCategory[] = [ + { + id: 'inputbar', + title: 'Eingabefeld', + shortcuts: [ + { + keys: ['Enter'], + description: 'Auswahl bestätigen / Erstellen', + category: 'inputbar', + }, + { + keys: ['Cmd', 'Enter'], + altKeys: ['Ctrl', 'Enter'], + description: 'Direkt erstellen', + category: 'inputbar', + }, + { + keys: ['Esc'], + description: 'Schließen & Eingabe löschen', + category: 'inputbar', + }, + { + keys: ['↑', '↓'], + description: 'Durch Ergebnisse navigieren', + category: 'inputbar', + }, + { + keys: ['Rechtsklick'], + description: 'Einstellungen öffnen', + category: 'inputbar', + }, + ], + }, + { + id: 'dialogs', + title: 'Dialoge', + shortcuts: [ + { + keys: ['Esc'], + description: 'Dialog schließen', + category: 'dialogs', + }, + ], + }, +]; + +/** + * Common syntax patterns shared across all apps with InputBar + */ +export const COMMON_SYNTAX: SyntaxGroup[] = [ + { + title: 'Kategorien & Tags', + items: [ + { + pattern: '#tag', + description: 'Tag hinzufügen', + examples: ['#arbeit', '#privat', '#wichtig'], + color: 'primary', + }, + { + pattern: '@name', + description: 'Kalender oder Projekt zuweisen', + examples: ['@team', '@privat', '@projekt'], + color: 'success', + }, + ], + }, + { + title: 'Zeit & Datum', + items: [ + { + pattern: 'Datum', + description: 'Natürliche Datumsangaben', + examples: ['heute', 'morgen', 'montag', 'in 3 tagen', 'nächste woche'], + color: 'accent', + }, + { + pattern: 'Uhrzeit', + description: 'Zeitangaben', + examples: ['14:00', '9 uhr', 'um 15:30'], + color: 'accent', + }, + ], + }, + { + title: 'Priorität', + items: [ + { + pattern: 'Priorität', + description: 'Dringlichkeit festlegen', + examples: [ + { text: '!!!', label: 'dringend', color: 'error' }, + { text: '!!', label: 'hoch', color: 'warning' }, + { text: '!', label: 'normal', color: 'warning-soft' }, + ], + color: 'error', + }, + ], + }, +]; + +/** + * Default live example for syntax highlighting demo + */ +export const DEFAULT_LIVE_EXAMPLE = { + text: 'Meeting mit Team morgen 14:00 @arbeit #wichtig', + highlights: [ + { type: 'text' as const, content: 'Meeting mit Team ' }, + { type: 'date' as const, content: 'morgen' }, + { type: 'text' as const, content: ' ' }, + { type: 'time' as const, content: '14:00' }, + { type: 'text' as const, content: ' ' }, + { type: 'reference' as const, content: '@arbeit' }, + { type: 'text' as const, content: ' ' }, + { type: 'tag' as const, content: '#wichtig' }, + ], +}; diff --git a/packages/shared-ui/src/help/index.ts b/packages/shared-ui/src/help/index.ts new file mode 100644 index 000000000..cf2bbb318 --- /dev/null +++ b/packages/shared-ui/src/help/index.ts @@ -0,0 +1,18 @@ +// Help Components +export { default as HelpModal } from './HelpModal.svelte'; +export { default as KeyboardShortcutsPanel } from './KeyboardShortcutsPanel.svelte'; +export { default as SyntaxHelpPanel } from './SyntaxHelpPanel.svelte'; + +// Types +export type { + KeyboardShortcut, + ShortcutCategory, + SyntaxColor, + SyntaxExample, + SyntaxPattern, + SyntaxGroup, + HelpModalConfig, +} from './types'; + +// Constants +export { COMMON_SHORTCUTS, COMMON_SYNTAX, DEFAULT_LIVE_EXAMPLE } from './constants'; diff --git a/packages/shared-ui/src/help/types.ts b/packages/shared-ui/src/help/types.ts new file mode 100644 index 000000000..6fca61d09 --- /dev/null +++ b/packages/shared-ui/src/help/types.ts @@ -0,0 +1,93 @@ +import type { Component } from 'svelte'; + +/** + * Represents a single keyboard shortcut + */ +export interface KeyboardShortcut { + /** Keys to press, e.g. ['Cmd', 'Enter'] or ['↑', '↓'] */ + keys: string[]; + /** Description of what the shortcut does */ + description: string; + /** Category ID for grouping */ + category: string; + /** Alternative keys (e.g. Ctrl instead of Cmd) */ + altKeys?: string[]; +} + +/** + * A category/group of related shortcuts + */ +export interface ShortcutCategory { + /** Unique identifier */ + id: string; + /** Display title */ + title: string; + /** Optional icon component */ + icon?: Component; + /** Shortcuts in this category */ + shortcuts: KeyboardShortcut[]; +} + +/** + * Color variants for syntax highlighting + */ +export type SyntaxColor = 'primary' | 'success' | 'accent' | 'error' | 'warning' | 'warning-soft'; + +/** + * A syntax example - can be a simple string or an object with label + */ +export type SyntaxExample = + | string + | { + text: string; + label?: string; + color?: SyntaxColor; + }; + +/** + * A syntax pattern that can be used in the InputBar + */ +export interface SyntaxPattern { + /** The pattern syntax, e.g. '#tag', '@name', 'Datum' */ + pattern: string; + /** Description of what the pattern does */ + description: string; + /** Example usages */ + examples: SyntaxExample[]; + /** Color for highlighting */ + color: SyntaxColor; + /** Optional icon component */ + icon?: Component; +} + +/** + * A group of related syntax patterns + */ +export interface SyntaxGroup { + /** Group title */ + title: string; + /** Patterns in this group */ + items: SyntaxPattern[]; +} + +/** + * Configuration for the HelpModal + */ +export interface HelpModalConfig { + /** Shortcut categories to display */ + shortcuts?: ShortcutCategory[]; + /** Syntax groups to display */ + syntax?: SyntaxGroup[]; + /** Whether to show tabs (auto-detected if both shortcuts and syntax are provided) */ + showTabs?: boolean; + /** Default tab to show */ + defaultTab?: 'shortcuts' | 'syntax'; + /** Live example text for syntax highlighting demo */ + liveExample?: { + text: string; + highlights: Array<{ + type: 'text' | 'tag' | 'reference' | 'date' | 'time' | 'priority'; + content: string; + }>; + }; +} diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index 7ccc8a9b6..d55e11ae9 100644 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -172,3 +172,22 @@ export type { // Context Menu export { ContextMenu, createContextMenuState } from './context-menu'; export type { ContextMenuItem, ContextMenuState } from './context-menu'; + +// Help Components +export { + HelpModal, + KeyboardShortcutsPanel as HelpKeyboardShortcutsPanel, + SyntaxHelpPanel, + COMMON_SHORTCUTS, + COMMON_SYNTAX, + DEFAULT_LIVE_EXAMPLE, +} from './help'; +export type { + KeyboardShortcut as HelpKeyboardShortcut, + ShortcutCategory, + SyntaxColor, + SyntaxExample, + SyntaxPattern, + SyntaxGroup, + HelpModalConfig, +} from './help'; diff --git a/packages/shared-ui/src/organisms/Modal.svelte b/packages/shared-ui/src/organisms/Modal.svelte index 0e33ba862..efeeec703 100644 --- a/packages/shared-ui/src/organisms/Modal.svelte +++ b/packages/shared-ui/src/organisms/Modal.svelte @@ -53,7 +53,8 @@
e.key === 'Enter' && handleBackdropClick(e as unknown as MouseEvent)} role="dialog" diff --git a/packages/shared-ui/src/quick-input/InputBarHelpModal.svelte b/packages/shared-ui/src/quick-input/InputBarHelpModal.svelte index b0b773981..01889a9dd 100644 --- a/packages/shared-ui/src/quick-input/InputBarHelpModal.svelte +++ b/packages/shared-ui/src/quick-input/InputBarHelpModal.svelte @@ -1,6 +1,7 @@ - - {#if mode === 'shortcuts'} -
-
- {#each shortcuts as { keys, description }} -
- {keys} - {description} -
- {/each} -
-
- {:else} -
-

- Du kannst natürliche Sprache verwenden, um schnell Einträge zu erstellen. Die InputBar - erkennt automatisch Daten, Zeiten, Tags und mehr. -

- -
- {#each syntaxExamples as { icon: Icon, pattern, description, example }} -
-
- -
-
-
- {pattern} - {description} -
-
{example}
-
-
- {/each} -
- -
-

Beispiel:

- Meeting mit Team morgen 14:00 @arbeit #wichtig -
-
- {/if} -
- - +