mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 02:46:42 +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
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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue