From b4bb40802dbbe1a273eccd8d3d5cf585a5072aa4 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:06:32 +0200 Subject: [PATCH] chore(orbit): help modal with 9-section walk-through Co-Authored-By: Claude Opus 4.7 (1M context) --- src/App.tsx | 2 + src/components/OrbitHelpModal.tsx | 102 +++++++++++++++++++++++++++ src/components/OrbitSessionBar.tsx | 12 +++- src/components/OrbitStartTrigger.tsx | 6 +- src/locales/de.ts | 24 +++++++ src/locales/en.ts | 24 +++++++ src/store/helpModalStore.ts | 19 +++++ src/styles/components.css | 55 +++++++++++++++ 8 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 src/components/OrbitHelpModal.tsx create mode 100644 src/store/helpModalStore.ts diff --git a/src/App.tsx b/src/App.tsx index 6ac5c4fd..a04b62cd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -54,6 +54,7 @@ import SongInfoModal from './components/SongInfoModal'; import DownloadFolderModal from './components/DownloadFolderModal'; import GlobalConfirmModal from './components/GlobalConfirmModal'; import OrbitAccountPicker from './components/OrbitAccountPicker'; +import OrbitHelpModal from './components/OrbitHelpModal'; import { DragDropProvider } from './contexts/DragDropContext'; import TooltipPortal from './components/TooltipPortal'; import OverlayScrollArea from './components/OverlayScrollArea'; @@ -522,6 +523,7 @@ function AppShell() { + diff --git a/src/components/OrbitHelpModal.tsx b/src/components/OrbitHelpModal.tsx new file mode 100644 index 00000000..7d565671 --- /dev/null +++ b/src/components/OrbitHelpModal.tsx @@ -0,0 +1,102 @@ +import { useEffect } from 'react'; +import { createPortal } from 'react-dom'; +import { + X, Sparkles, Users, Share2, LogIn, MousePointerClick, + ListMusic, Inbox, Sliders, LogOut, +} from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { useHelpModalStore } from '../store/helpModalStore'; +import SettingsSubSection from './SettingsSubSection'; + +/** + * Orbit help modal. Rendered once at the app root; triggered from the + * launch popover ("How does this work?") and the in-session bar's help + * button. 9 accordion sections built on SettingsSubSection; all default + * closed so the modal opens compact. Does not touch playback. + */ +export default function OrbitHelpModal() { + const { t } = useTranslation(); + const { isOpen, close } = useHelpModalStore(); + + useEffect(() => { + if (!isOpen) return; + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') close(); }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [isOpen, close]); + + if (!isOpen) return null; + + const hostOnlyLabel = t('orbit.helpHostOnly'); + + return createPortal( +
{ if (e.target === e.currentTarget) close(); }} + role="dialog" + aria-modal="true" + aria-labelledby="orbit-help-title" + style={{ alignItems: 'center', paddingTop: 0 }} + > +
+ + +

+ {t('orbit.helpTitle')} +

+

{t('orbit.helpIntro')}

+ +
+ }> +

{t('orbit.helpSec1Body')}

+
+ + }> +

{t('orbit.helpSec2Body')}

+
+ {t('orbit.helpSec2WarnHead')} + {t('orbit.helpSec2WarnBody')} +
+
+ + }> +

{t('orbit.helpSec3Body')}

+
+ + }> +

{t('orbit.helpSec4Body')}

+
+ + }> +

{t('orbit.helpSec5Body')}

+
+ + }> +

{t('orbit.helpSec6Body')}

+
+ + } + > +

{t('orbit.helpSec7Body')}

+
+ + } + > +

{t('orbit.helpSec8Body')}

+
+ + }> +

{t('orbit.helpSec9Body')}

+
+
+
+
, + document.body, + ); +} diff --git a/src/components/OrbitSessionBar.tsx b/src/components/OrbitSessionBar.tsx index f0933b4c..35dd8d24 100644 --- a/src/components/OrbitSessionBar.tsx +++ b/src/components/OrbitSessionBar.tsx @@ -1,7 +1,8 @@ import { useEffect, useRef, useState } from 'react'; -import { X, RefreshCw, Shuffle, Settings2, Share2 } from 'lucide-react'; +import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useOrbitStore } from '../store/orbitStore'; +import { useHelpModalStore } from '../store/helpModalStore'; import { usePlayerStore, songToTrack } from '../store/playerStore'; import { getSong } from '../api/subsonic'; import { @@ -208,6 +209,15 @@ export default function OrbitSessionBar() { {t('orbit.catchUpLabel')} )} +