mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(shared-ui): add reusable HelpModal system with keyboard shortcuts and syntax panels
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
5ac8de722d
commit
c712cc7995
12 changed files with 1238 additions and 195 deletions
|
|
@ -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,
|
||||
|
|
|
|||
108
apps/calendar/apps/web/src/lib/config/helpConfig.ts
Normal file
108
apps/calendar/apps/web/src/lib/config/helpConfig.ts
Normal file
|
|
@ -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 };
|
||||
|
|
@ -89,9 +89,19 @@
|
|||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="context-menu-backdrop"
|
||||
onclick={onClose}
|
||||
onpointerdown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
oncontextmenu={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
></div>
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
183
packages/shared-ui/src/help/HelpModal.svelte
Normal file
183
packages/shared-ui/src/help/HelpModal.svelte
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<script lang="ts">
|
||||
import { Modal } from '../organisms';
|
||||
import { Keyboard, Hash, X } from '@manacore/shared-icons';
|
||||
import KeyboardShortcutsPanel from './KeyboardShortcutsPanel.svelte';
|
||||
import SyntaxHelpPanel from './SyntaxHelpPanel.svelte';
|
||||
import type { HelpModalConfig } from './types';
|
||||
|
||||
interface Props {
|
||||
/** Whether the modal is open */
|
||||
open: boolean;
|
||||
/** Close handler */
|
||||
onClose: () => void;
|
||||
/** Configuration for the modal content */
|
||||
config: HelpModalConfig;
|
||||
}
|
||||
|
||||
let { open, onClose, config }: Props = $props();
|
||||
|
||||
// Determine which tabs to show
|
||||
const hasShortcuts = $derived((config.shortcuts?.length ?? 0) > 0);
|
||||
const hasSyntax = $derived((config.syntax?.length ?? 0) > 0);
|
||||
const showTabs = $derived(config.showTabs ?? (hasShortcuts && hasSyntax));
|
||||
|
||||
// Active tab state
|
||||
let activeTab = $state<'shortcuts' | 'syntax'>(config.defaultTab ?? 'shortcuts');
|
||||
|
||||
// Reset to default tab when modal opens
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
activeTab = config.defaultTab ?? 'shortcuts';
|
||||
}
|
||||
});
|
||||
|
||||
// If only one type is available, show that one
|
||||
const effectiveTab = $derived(() => {
|
||||
if (!hasShortcuts) return 'syntax';
|
||||
if (!hasSyntax) return 'shortcuts';
|
||||
return activeTab;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal visible={open} {onClose} title="" showHeader={false} maxWidth="md">
|
||||
<div class="help-modal">
|
||||
<!-- Header with Tabs -->
|
||||
<div class="modal-header">
|
||||
{#if showTabs}
|
||||
<div class="tabs">
|
||||
{#if hasShortcuts}
|
||||
<button
|
||||
class="tab"
|
||||
class:active={effectiveTab() === 'shortcuts'}
|
||||
onclick={() => (activeTab = 'shortcuts')}
|
||||
>
|
||||
<Keyboard size={16} weight="bold" />
|
||||
<span>Tastenkürzel</span>
|
||||
</button>
|
||||
{/if}
|
||||
{#if hasSyntax}
|
||||
<button
|
||||
class="tab"
|
||||
class:active={effectiveTab() === 'syntax'}
|
||||
onclick={() => (activeTab = 'syntax')}
|
||||
>
|
||||
<Hash size={16} weight="bold" />
|
||||
<span>Syntax</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="header-title">
|
||||
{#if hasShortcuts}
|
||||
<Keyboard size={18} weight="bold" />
|
||||
<span>Tastenkürzel</span>
|
||||
{:else if hasSyntax}
|
||||
<Hash size={18} weight="bold" />
|
||||
<span>Syntax-Hilfe</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<button class="close-btn" onclick={onClose} aria-label="Schließen">
|
||||
<X size={18} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="modal-content">
|
||||
{#if effectiveTab() === 'shortcuts' && config.shortcuts}
|
||||
<KeyboardShortcutsPanel categories={config.shortcuts} />
|
||||
{:else if effectiveTab() === 'syntax' && config.syntax}
|
||||
<SyntaxHelpPanel
|
||||
groups={config.syntax}
|
||||
showLiveExample={!!config.liveExample}
|
||||
liveExample={config.liveExample}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.help-modal {
|
||||
margin: -1.5rem;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid hsl(var(--color-border));
|
||||
background: hsl(var(--color-muted) / 0.3);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
background: hsl(var(--color-muted) / 0.5);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: hsl(var(--color-background));
|
||||
color: hsl(var(--color-foreground));
|
||||
box-shadow: 0 1px 3px hsl(var(--color-foreground) / 0.1);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: hsl(var(--color-muted));
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.modal-content {
|
||||
padding: 1.25rem;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
251
packages/shared-ui/src/help/KeyboardShortcutsPanel.svelte
Normal file
251
packages/shared-ui/src/help/KeyboardShortcutsPanel.svelte
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
Keyboard,
|
||||
ArrowBendDownLeft,
|
||||
ArrowsVertical,
|
||||
X,
|
||||
Mouse,
|
||||
Sparkle,
|
||||
ChatCircle,
|
||||
NavigationArrow,
|
||||
} from '@manacore/shared-icons';
|
||||
import type { ShortcutCategory } from './types';
|
||||
|
||||
interface Props {
|
||||
/** Shortcut categories to display */
|
||||
categories: ShortcutCategory[];
|
||||
/** Compact mode for smaller displays */
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
let { categories, compact = false }: Props = $props();
|
||||
|
||||
// Default icons for common categories
|
||||
const categoryIcons: Record<string, typeof Keyboard> = {
|
||||
inputbar: Keyboard,
|
||||
dialogs: ChatCircle,
|
||||
navigation: NavigationArrow,
|
||||
};
|
||||
|
||||
// Default icons for common shortcuts based on first key
|
||||
const shortcutIcons: Record<string, typeof Keyboard> = {
|
||||
Enter: ArrowBendDownLeft,
|
||||
Cmd: Sparkle,
|
||||
Ctrl: Sparkle,
|
||||
Esc: X,
|
||||
'↑': ArrowsVertical,
|
||||
'↓': ArrowsVertical,
|
||||
Rechtsklick: Mouse,
|
||||
};
|
||||
|
||||
function getShortcutIcon(keys: string[]) {
|
||||
for (const key of keys) {
|
||||
if (shortcutIcons[key]) {
|
||||
return shortcutIcons[key];
|
||||
}
|
||||
}
|
||||
return Keyboard;
|
||||
}
|
||||
|
||||
function getCategoryIcon(category: ShortcutCategory) {
|
||||
if (category.icon) return category.icon;
|
||||
return categoryIcons[category.id] || Keyboard;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="shortcuts-panel" class:compact>
|
||||
{#each categories as category}
|
||||
{@const CategoryIcon = getCategoryIcon(category)}
|
||||
<div class="category">
|
||||
<div class="category-header">
|
||||
<CategoryIcon size={16} weight="bold" />
|
||||
<span>{category.title}</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-list">
|
||||
{#each category.shortcuts as shortcut}
|
||||
{@const ShortcutIcon = getShortcutIcon(shortcut.keys)}
|
||||
<div class="shortcut-item">
|
||||
<div class="shortcut-icon">
|
||||
<ShortcutIcon size={18} weight="bold" />
|
||||
</div>
|
||||
<div class="shortcut-keys">
|
||||
{#each shortcut.keys as key, i}
|
||||
{#if i > 0}<span class="key-separator">+</span>{/if}
|
||||
<kbd>{key}</kbd>
|
||||
{/each}
|
||||
{#if shortcut.altKeys && !compact}
|
||||
<span class="alt-keys">
|
||||
oder
|
||||
{#each shortcut.altKeys as key, i}
|
||||
{#if i > 0}<span class="key-separator">+</span>{/if}
|
||||
<kbd>{key}</kbd>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="shortcut-desc">{shortcut.description}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.shortcuts-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
.shortcuts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.shortcut-item {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
padding: 0.625rem 0.75rem;
|
||||
background: hsl(var(--color-muted) / 0.3);
|
||||
border-radius: var(--radius-md);
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.shortcut-item:hover {
|
||||
background: hsl(var(--color-muted) / 0.5);
|
||||
}
|
||||
|
||||
.shortcut-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: hsl(var(--color-primary) / 0.1);
|
||||
color: hsl(var(--color-primary));
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.shortcut-keys {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.shortcut-keys kbd {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 28px;
|
||||
height: 28px;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
color: hsl(var(--color-foreground));
|
||||
background: hsl(var(--color-background));
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 6px;
|
||||
box-shadow:
|
||||
0 1px 0 1px hsl(var(--color-border)),
|
||||
0 2px 0 hsl(var(--color-muted));
|
||||
}
|
||||
|
||||
.key-separator {
|
||||
font-size: 0.8125rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
margin: 0 0.1875rem;
|
||||
}
|
||||
|
||||
.alt-keys {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-left: 0.5rem;
|
||||
font-size: 0.8125rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
.shortcut-desc {
|
||||
font-size: 0.9375rem;
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
/* Compact mode */
|
||||
.shortcuts-panel.compact .shortcut-item {
|
||||
grid-template-columns: 24px 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
padding: 0.375rem 0.5rem;
|
||||
}
|
||||
|
||||
.shortcuts-panel.compact .shortcut-icon {
|
||||
grid-row: span 2;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.shortcuts-panel.compact .shortcut-keys {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.shortcuts-panel.compact .shortcut-desc {
|
||||
grid-column: 2;
|
||||
font-size: 0.6875rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
.shortcuts-panel.compact .alt-keys {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
.shortcut-item {
|
||||
grid-template-columns: 24px 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
|
||||
.shortcut-icon {
|
||||
grid-row: span 2;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.shortcut-keys {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.shortcut-desc {
|
||||
grid-column: 2;
|
||||
font-size: 0.6875rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
.alt-keys {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
415
packages/shared-ui/src/help/SyntaxHelpPanel.svelte
Normal file
415
packages/shared-ui/src/help/SyntaxHelpPanel.svelte
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
<script lang="ts">
|
||||
import { Hash, At, Calendar, Clock, ArrowFatLineRight } from '@manacore/shared-icons';
|
||||
import type { SyntaxGroup, SyntaxColor } from './types';
|
||||
|
||||
interface Props {
|
||||
/** Syntax groups to display */
|
||||
groups: SyntaxGroup[];
|
||||
/** Show live example at the bottom */
|
||||
showLiveExample?: boolean;
|
||||
/** Custom live example */
|
||||
liveExample?: {
|
||||
text: string;
|
||||
highlights: Array<{
|
||||
type: 'text' | 'tag' | 'reference' | 'date' | 'time' | 'priority';
|
||||
content: string;
|
||||
}>;
|
||||
};
|
||||
/** Intro text shown at the top */
|
||||
introText?: string;
|
||||
/** Compact mode for smaller displays */
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
groups,
|
||||
showLiveExample = true,
|
||||
liveExample,
|
||||
introText = 'Erstelle Einträge mit natürlicher Sprache. Schreibe einfach los – die InputBar erkennt automatisch Daten, Zeiten, Tags und mehr.',
|
||||
compact = false,
|
||||
}: Props = $props();
|
||||
|
||||
// Default icons for common patterns
|
||||
const patternIcons: Record<string, typeof Hash> = {
|
||||
'#tag': Hash,
|
||||
'@name': At,
|
||||
Datum: Calendar,
|
||||
Uhrzeit: Clock,
|
||||
Priorität: ArrowFatLineRight,
|
||||
};
|
||||
|
||||
function getPatternIcon(pattern: string) {
|
||||
return patternIcons[pattern] || Hash;
|
||||
}
|
||||
|
||||
// Default live example if none provided
|
||||
const defaultLiveExample = {
|
||||
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' },
|
||||
],
|
||||
};
|
||||
|
||||
const example = $derived(liveExample ?? defaultLiveExample);
|
||||
</script>
|
||||
|
||||
<div class="syntax-panel" class:compact>
|
||||
{#if introText && !compact}
|
||||
<p class="intro-text">{introText}</p>
|
||||
{/if}
|
||||
|
||||
{#each groups as group}
|
||||
<div class="syntax-group">
|
||||
<h3 class="group-title">{group.title}</h3>
|
||||
<div class="group-items">
|
||||
{#each group.items as item}
|
||||
{@const Icon = item.icon ?? getPatternIcon(item.pattern)}
|
||||
<div class="syntax-item">
|
||||
<div class="syntax-icon" data-color={item.color}>
|
||||
<Icon size={compact ? 16 : 20} weight="bold" />
|
||||
</div>
|
||||
<div class="syntax-content">
|
||||
<div class="syntax-header">
|
||||
<code class="pattern" data-color={item.color}>{item.pattern}</code>
|
||||
<span class="syntax-desc">{item.description}</span>
|
||||
</div>
|
||||
<div class="syntax-examples">
|
||||
{#each item.examples as ex}
|
||||
{#if typeof ex === 'string'}
|
||||
<span class="example-tag" data-color={item.color}>{ex}</span>
|
||||
{:else}
|
||||
<span class="example-tag" data-color={ex.color ?? item.color}>
|
||||
{ex.text}
|
||||
{#if ex.label}
|
||||
<span class="example-label">{ex.label}</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#if showLiveExample && !compact}
|
||||
<div class="live-example">
|
||||
<div class="live-label">Beispiel-Eingabe</div>
|
||||
<div class="live-input">
|
||||
{#each example.highlights as hl}
|
||||
<span class="hl-{hl.type}">{hl.content}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.syntax-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.syntax-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.group-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.syntax-item {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--color-muted) / 0.3);
|
||||
border-radius: var(--radius-md);
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.syntax-item:hover {
|
||||
background: hsl(var(--color-muted) / 0.5);
|
||||
}
|
||||
|
||||
.syntax-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.syntax-icon[data-color='primary'] {
|
||||
background: hsl(var(--color-primary) / 0.15);
|
||||
color: hsl(var(--color-primary));
|
||||
}
|
||||
|
||||
.syntax-icon[data-color='success'] {
|
||||
background: hsl(var(--color-success) / 0.15);
|
||||
color: hsl(var(--color-success));
|
||||
}
|
||||
|
||||
.syntax-icon[data-color='accent'] {
|
||||
background: hsl(var(--color-accent, 262 83% 58%) / 0.15);
|
||||
color: hsl(var(--color-accent, 262 83% 58%));
|
||||
}
|
||||
|
||||
.syntax-icon[data-color='error'] {
|
||||
background: hsl(var(--color-error) / 0.15);
|
||||
color: hsl(var(--color-error));
|
||||
}
|
||||
|
||||
.syntax-icon[data-color='warning'] {
|
||||
background: hsl(var(--color-warning, 25 95% 53%) / 0.15);
|
||||
color: hsl(var(--color-warning, 25 95% 53%));
|
||||
}
|
||||
|
||||
.syntax-icon[data-color='warning-soft'] {
|
||||
background: hsl(var(--color-warning, 48 96% 53%) / 0.15);
|
||||
color: hsl(var(--color-warning, 48 96% 53%));
|
||||
}
|
||||
|
||||
.syntax-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.syntax-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.pattern {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
padding: 0.1875rem 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.pattern[data-color='primary'] {
|
||||
color: hsl(var(--color-primary));
|
||||
background: hsl(var(--color-primary) / 0.15);
|
||||
}
|
||||
|
||||
.pattern[data-color='success'] {
|
||||
color: hsl(var(--color-success));
|
||||
background: hsl(var(--color-success) / 0.15);
|
||||
}
|
||||
|
||||
.pattern[data-color='accent'] {
|
||||
color: hsl(var(--color-accent, 262 83% 58%));
|
||||
background: hsl(var(--color-accent, 262 83% 58%) / 0.15);
|
||||
}
|
||||
|
||||
.pattern[data-color='error'] {
|
||||
color: hsl(var(--color-error));
|
||||
background: hsl(var(--color-error) / 0.15);
|
||||
}
|
||||
|
||||
.pattern[data-color='warning'] {
|
||||
color: hsl(var(--color-warning, 25 95% 53%));
|
||||
background: hsl(var(--color-warning, 25 95% 53%) / 0.15);
|
||||
}
|
||||
|
||||
.pattern[data-color='warning-soft'] {
|
||||
color: hsl(var(--color-warning, 48 96% 53%));
|
||||
background: hsl(var(--color-warning, 48 96% 53%) / 0.15);
|
||||
}
|
||||
|
||||
.syntax-desc {
|
||||
font-size: 0.9375rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
.syntax-examples {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.example-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.example-tag[data-color='primary'] {
|
||||
color: hsl(var(--color-primary));
|
||||
background: hsl(var(--color-primary) / 0.1);
|
||||
}
|
||||
|
||||
.example-tag[data-color='success'] {
|
||||
color: hsl(var(--color-success));
|
||||
background: hsl(var(--color-success) / 0.1);
|
||||
}
|
||||
|
||||
.example-tag[data-color='accent'] {
|
||||
color: hsl(var(--color-accent, 262 83% 58%));
|
||||
background: hsl(var(--color-accent, 262 83% 58%) / 0.1);
|
||||
}
|
||||
|
||||
.example-tag[data-color='error'] {
|
||||
color: hsl(var(--color-error));
|
||||
background: hsl(var(--color-error) / 0.1);
|
||||
}
|
||||
|
||||
.example-tag[data-color='warning'] {
|
||||
color: hsl(var(--color-warning, 25 95% 53%));
|
||||
background: hsl(var(--color-warning, 25 95% 53%) / 0.1);
|
||||
}
|
||||
|
||||
.example-tag[data-color='warning-soft'] {
|
||||
color: hsl(var(--color-warning, 48 96% 53%));
|
||||
background: hsl(var(--color-warning, 48 96% 53%) / 0.1);
|
||||
}
|
||||
|
||||
.example-label {
|
||||
font-size: 0.625rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Live Example */
|
||||
.live-example {
|
||||
padding: 1rem;
|
||||
background: hsl(var(--color-muted) / 0.5);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px dashed hsl(var(--color-border));
|
||||
}
|
||||
|
||||
.live-label {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
margin-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.live-input {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
.live-input :global(.hl-text) {
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
.live-input :global(.hl-tag) {
|
||||
color: hsl(var(--color-primary));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.live-input :global(.hl-reference) {
|
||||
color: hsl(var(--color-success));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.live-input :global(.hl-date),
|
||||
.live-input :global(.hl-time) {
|
||||
color: hsl(var(--color-accent, 262 83% 58%));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.live-input :global(.hl-priority) {
|
||||
color: hsl(var(--color-error));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Compact mode */
|
||||
.syntax-panel.compact {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.syntax-panel.compact .syntax-item {
|
||||
padding: 0.5rem 0.625rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.syntax-panel.compact .syntax-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.syntax-panel.compact .syntax-header {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.syntax-panel.compact .pattern {
|
||||
font-size: 0.6875rem;
|
||||
}
|
||||
|
||||
.syntax-panel.compact .syntax-desc {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.syntax-panel.compact .example-tag {
|
||||
font-size: 0.625rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
.syntax-item {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.syntax-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.syntax-header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.live-example {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.live-input {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
122
packages/shared-ui/src/help/constants.ts
Normal file
122
packages/shared-ui/src/help/constants.ts
Normal file
|
|
@ -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' },
|
||||
],
|
||||
};
|
||||
18
packages/shared-ui/src/help/index.ts
Normal file
18
packages/shared-ui/src/help/index.ts
Normal file
|
|
@ -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';
|
||||
93
packages/shared-ui/src/help/types.ts
Normal file
93
packages/shared-ui/src/help/types.ts
Normal file
|
|
@ -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;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@
|
|||
<!-- Modal Backdrop -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
||||
class="fixed inset-0 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
||||
style="z-index: 9990;"
|
||||
onclick={handleBackdropClick}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleBackdropClick(e as unknown as MouseEvent)}
|
||||
role="dialog"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Modal } from '../organisms';
|
||||
import { Keyboard, Hash, At, Calendar, Clock, ArrowFatLineRight } from '@manacore/shared-icons';
|
||||
import { HelpModal } from '../help';
|
||||
import { COMMON_SHORTCUTS, COMMON_SYNTAX, DEFAULT_LIVE_EXAMPLE } from '../help';
|
||||
import type { HelpModalConfig } from '../help';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -10,196 +11,13 @@
|
|||
|
||||
let { open, onClose, mode = 'shortcuts' }: Props = $props();
|
||||
|
||||
const shortcuts = [
|
||||
{ keys: 'Enter', description: 'Ausgewähltes Item auswählen oder erstellen' },
|
||||
{ keys: '⌘/Ctrl + Enter', description: 'Direkt erstellen (ohne Auswahl)' },
|
||||
{ keys: 'Esc', description: 'Eingabe löschen und schließen' },
|
||||
{ keys: '↑ / ↓', description: 'In Ergebnissen navigieren' },
|
||||
{ keys: 'Rechtsklick', description: 'Einstellungen öffnen' },
|
||||
];
|
||||
|
||||
const syntaxExamples = [
|
||||
{ icon: Hash, pattern: '#tag', description: 'Tag hinzufügen', example: '#arbeit #wichtig' },
|
||||
{
|
||||
icon: At,
|
||||
pattern: '@referenz',
|
||||
description: 'Kalender/Projekt zuweisen',
|
||||
example: '@privat @team',
|
||||
},
|
||||
{
|
||||
icon: Calendar,
|
||||
pattern: 'Datum',
|
||||
description: 'Datum festlegen',
|
||||
example: 'heute, morgen, montag, in 3 tagen',
|
||||
},
|
||||
{ icon: Clock, pattern: 'Zeit', description: 'Uhrzeit festlegen', example: '14:00, 9 uhr' },
|
||||
{
|
||||
icon: ArrowFatLineRight,
|
||||
pattern: 'Priorität',
|
||||
description: 'Priorität setzen',
|
||||
example: '!!! dringend, !! wichtig',
|
||||
},
|
||||
];
|
||||
// Build the config for HelpModal using common defaults
|
||||
const config: HelpModalConfig = {
|
||||
shortcuts: COMMON_SHORTCUTS,
|
||||
syntax: COMMON_SYNTAX,
|
||||
defaultTab: mode,
|
||||
liveExample: DEFAULT_LIVE_EXAMPLE,
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal visible={open} {onClose} title={mode === 'shortcuts' ? 'Tastenkürzel' : 'Syntax-Hilfe'}>
|
||||
{#if mode === 'shortcuts'}
|
||||
<div class="help-content">
|
||||
<div class="shortcuts-list">
|
||||
{#each shortcuts as { keys, description }}
|
||||
<div class="shortcut-row">
|
||||
<kbd class="shortcut-keys">{keys}</kbd>
|
||||
<span class="shortcut-desc">{description}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="help-content">
|
||||
<p class="help-intro">
|
||||
Du kannst natürliche Sprache verwenden, um schnell Einträge zu erstellen. Die InputBar
|
||||
erkennt automatisch Daten, Zeiten, Tags und mehr.
|
||||
</p>
|
||||
|
||||
<div class="syntax-list">
|
||||
{#each syntaxExamples as { icon: Icon, pattern, description, example }}
|
||||
<div class="syntax-row">
|
||||
<div class="syntax-icon">
|
||||
<Icon size={18} />
|
||||
</div>
|
||||
<div class="syntax-info">
|
||||
<div class="syntax-pattern">
|
||||
<code>{pattern}</code>
|
||||
<span class="syntax-desc">{description}</span>
|
||||
</div>
|
||||
<div class="syntax-example">{example}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="help-example">
|
||||
<p class="example-label">Beispiel:</p>
|
||||
<code class="example-text">Meeting mit Team morgen 14:00 @arbeit #wichtig</code>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.help-content {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.help-intro {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
margin-bottom: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.shortcuts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.shortcut-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.shortcut-keys {
|
||||
min-width: 140px;
|
||||
padding: 0.375rem 0.625rem;
|
||||
font-size: 0.75rem;
|
||||
font-family: inherit;
|
||||
background: hsl(var(--color-muted));
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: var(--radius-sm);
|
||||
color: hsl(var(--color-foreground));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.shortcut-desc {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--color-foreground));
|
||||
}
|
||||
|
||||
.syntax-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.syntax-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.syntax-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: hsl(var(--color-primary) / 0.1);
|
||||
color: hsl(var(--color-primary));
|
||||
border-radius: var(--radius-md);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.syntax-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.syntax-pattern {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.syntax-pattern code {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--color-primary));
|
||||
background: hsl(var(--color-primary) / 0.1);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.syntax-desc {
|
||||
font-size: 0.8125rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
.syntax-example {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.help-example {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: hsl(var(--color-muted) / 0.5);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.example-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.example-text {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--color-foreground));
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
<HelpModal {open} {onClose} {config} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue