mirror of
https://github.com/abusoww/tuxmate.git
synced 2026-04-18 00:03:23 +02:00
feat: refined UI components and updated app data
This commit is contained in:
@@ -6,234 +6,286 @@ import { HelpCircle, X } from 'lucide-react';
|
||||
import { analytics } from '@/lib/analytics';
|
||||
|
||||
/**
|
||||
* HowItWorks - Interactive help popup with quick start guide
|
||||
* HowItWorks - Interactive help modal with quick start guide
|
||||
*
|
||||
* Displays a popup with:
|
||||
* Displays a centered modal with backdrop blur, containing:
|
||||
* - Quick start steps for using TuxMate
|
||||
* - Info about unavailable apps
|
||||
* - Arch/AUR specific info
|
||||
* - Keyboard shortcuts
|
||||
* - Pro tips
|
||||
*
|
||||
* @example
|
||||
* <HowItWorks />
|
||||
*/
|
||||
export function HowItWorks() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const popupRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Close on click outside
|
||||
// Lock body scroll when modal is open
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (
|
||||
triggerRef.current && !triggerRef.current.contains(e.target as Node) &&
|
||||
popupRef.current && !popupRef.current.contains(e.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
// Global keyboard shortcut: ? to toggle modal
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Ignore if typing in input
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
||||
|
||||
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
|
||||
e.preventDefault();
|
||||
if (isOpen) {
|
||||
handleClose();
|
||||
} else {
|
||||
handleOpen();
|
||||
}
|
||||
}
|
||||
|
||||
// Close on Escape
|
||||
if (e.key === 'Escape' && isOpen) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [isOpen]);
|
||||
|
||||
// Close on Escape
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setIsOpen(false);
|
||||
};
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
return () => document.removeEventListener('keydown', handleEscape);
|
||||
}, [isOpen]);
|
||||
|
||||
const getPopupPosition = () => {
|
||||
if (!triggerRef.current) return { top: 0, left: 0 };
|
||||
const rect = triggerRef.current.getBoundingClientRect();
|
||||
return {
|
||||
top: rect.bottom + 12,
|
||||
left: Math.max(8, Math.min(rect.left, window.innerWidth - 420)),
|
||||
};
|
||||
const handleOpen = () => {
|
||||
setIsClosing(false);
|
||||
setIsOpen(true);
|
||||
analytics.helpOpened();
|
||||
};
|
||||
|
||||
const pos = isOpen ? getPopupPosition() : { top: 0, left: 0 };
|
||||
const handleClose = () => {
|
||||
setIsClosing(true);
|
||||
analytics.helpClosed();
|
||||
// Wait for exit animation to finish
|
||||
setTimeout(() => {
|
||||
setIsOpen(false);
|
||||
setIsClosing(false);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const popup = isOpen && mounted ? (
|
||||
<div
|
||||
ref={popupRef}
|
||||
className="how-it-works-popup bg-[var(--bg-secondary)] backdrop-blur-xl border border-[var(--border-primary)] shadow-2xl"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: pos.top,
|
||||
left: pos.left,
|
||||
zIndex: 99999,
|
||||
borderRadius: '16px',
|
||||
width: '400px',
|
||||
maxWidth: 'calc(100vw - 16px)',
|
||||
maxHeight: 'min(70vh, 600px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
animation: 'popupSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* Header - fixed */}
|
||||
<div className="flex items-center justify-between gap-2 p-4 pb-3 border-b border-[var(--border-primary)] shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full bg-[var(--accent)]/20 flex items-center justify-center">
|
||||
<HelpCircle className="w-4 h-4 text-[var(--accent)]" />
|
||||
const modal = (
|
||||
<>
|
||||
{/* Backdrop with blur */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/30 backdrop-blur-sm z-[99998]"
|
||||
onClick={handleClose}
|
||||
style={{
|
||||
animation: isClosing
|
||||
? 'fadeOut 0.2s ease-out forwards'
|
||||
: 'fadeIn 0.25s ease-out'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="how-it-works-title"
|
||||
className="fixed bg-[var(--bg-secondary)] border border-[var(--border-primary)] shadow-2xl z-[99999]"
|
||||
style={{
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
borderRadius: '20px',
|
||||
width: '440px',
|
||||
maxWidth: 'calc(100vw - 32px)',
|
||||
maxHeight: 'min(80vh, 650px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
animation: isClosing
|
||||
? 'modalSlideOut 0.2s ease-out forwards'
|
||||
: 'modalSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1)',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-3 px-5 py-4 border-b border-[var(--border-primary)] shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-[var(--accent)]/15 flex items-center justify-center">
|
||||
<HelpCircle className="w-5 h-5 text-[var(--accent)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 id="how-it-works-title" className="text-lg font-semibold text-[var(--text-primary)]">How TuxMate Works</h3>
|
||||
<p className="text-xs text-[var(--text-muted)]">Quick guide & keyboard shortcuts</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-[var(--bg-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Scrollable content */}
|
||||
<div className="flex-1 overflow-y-auto p-5 space-y-5" style={{ scrollbarGutter: 'stable' }}>
|
||||
{/* Quick Start Steps */}
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-[var(--text-primary)]">How TuxMate Works</h3>
|
||||
<p className="text-xs text-[var(--text-muted)]">Quick guide & tips</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="w-7 h-7 flex items-center justify-center rounded-full hover:bg-[var(--bg-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Scrollable content */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-5" style={{ scrollbarGutter: 'stable' }}>
|
||||
{/* Quick Start Steps */}
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Quick Start</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">1</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Select your distro from the dropdown</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">2</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Check the apps you want to install</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">3</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Copy the command or download the script</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">4</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Paste in terminal (<code className="text-xs bg-[var(--bg-tertiary)] px-1 py-0.5 rounded">Ctrl+Shift+V</code>) and run</p>
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Quick Start</h4>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">1</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Select your distro from the dropdown</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">2</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Check the apps you want to install</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">3</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Copy the command or download the script</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-[var(--accent)]/20 flex items-center justify-center text-[10px] font-bold text-[var(--accent)] shrink-0">4</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Paste in terminal (<code className="text-xs bg-[var(--bg-tertiary)] px-1 py-0.5 rounded">Ctrl+Shift+V</code>) and run</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Unavailable Apps */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">App Not Available?</h4>
|
||||
<div className="space-y-2.5 text-xs text-[var(--text-muted)] leading-relaxed">
|
||||
<p>Greyed-out apps aren't in your distro's repos. Here's what you can do:</p>
|
||||
<ul className="space-y-2 ml-2">
|
||||
{/* Unavailable Apps */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">App Not Available?</h4>
|
||||
<div className="space-y-2.5 text-xs text-[var(--text-muted)] leading-relaxed">
|
||||
<p>Greyed-out apps aren't in your distro's repos. Here's what you can do:</p>
|
||||
<ul className="space-y-2 ml-2">
|
||||
<li className="flex gap-2">
|
||||
<span className="text-[var(--accent)]">•</span>
|
||||
<span><strong className="text-[var(--text-secondary)]">Use Flatpak/Snap:</strong> Switch to Flatpak or Snap in the distro selector for universal packages</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-[var(--accent)]">•</span>
|
||||
<span><strong className="text-[var(--text-secondary)]">Download from website:</strong> Visit the app's official site and grab the <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code>, <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code>, or <code className="bg-[var(--bg-tertiary)] px-1 rounded">.AppImage</code></span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-[var(--accent)]">•</span>
|
||||
<span><strong className="text-[var(--text-secondary)]">Hover the ⓘ icon:</strong> Some unavailable apps show links to alternative download methods</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arch & AUR */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Arch Linux & AUR</h4>
|
||||
<p className="text-xs text-[var(--text-muted)] leading-relaxed">
|
||||
Some Arch packages are in the <strong className="text-[var(--text-secondary)]">AUR</strong> (Arch User Repository).
|
||||
TuxMate uses <code className="bg-[var(--bg-tertiary)] px-1 rounded">yay</code> or <code className="bg-[var(--bg-tertiary)] px-1 rounded">paru</code> to install these.
|
||||
When selecting AUR packages, a popup will ask which helper you have. You can switch between helpers anytime using <kbd className="px-1 py-0.5 bg-[var(--bg-tertiary)] rounded text-[10px]">1</kbd> (yay) or <kbd className="px-1 py-0.5 bg-[var(--bg-tertiary)] rounded text-[10px]">2</kbd> (paru).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Keyboard Shortcuts */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Keyboard Shortcuts</h4>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">↑↓←→</kbd>
|
||||
<span className="text-[var(--text-muted)]">Navigate</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">hjkl</kbd>
|
||||
<span className="text-[var(--text-muted)]">Vim navigation</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Space</kbd>
|
||||
<span className="text-[var(--text-muted)]">Toggle selection</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">/</kbd>
|
||||
<span className="text-[var(--text-muted)]">Search apps</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">y</kbd>
|
||||
<span className="text-[var(--text-muted)]">Copy command</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">d</kbd>
|
||||
<span className="text-[var(--text-muted)]">Download script</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">c</kbd>
|
||||
<span className="text-[var(--text-muted)]">Clear selection</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">t</kbd>
|
||||
<span className="text-[var(--text-muted)]">Toggle theme</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Tab</kbd>
|
||||
<span className="text-[var(--text-muted)]">Open preview</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Esc</kbd>
|
||||
<span className="text-[var(--text-muted)]">Close popups</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">?</kbd>
|
||||
<span className="text-[var(--text-muted)]">This help</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pro Tips */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Pro Tips</h4>
|
||||
<ul className="space-y-2 text-xs text-[var(--text-muted)] leading-relaxed">
|
||||
<li className="flex gap-2">
|
||||
<span className="text-[var(--accent)]">•</span>
|
||||
<span><strong className="text-[var(--text-secondary)]">Use Flatpak/Snap:</strong> Switch to Flatpak or Snap in the distro selector for universal packages</span>
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>The <strong className="text-[var(--text-secondary)]">download button</strong> gives you a full shell script with progress tracking, error handling, and a summary</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-[var(--accent)]">•</span>
|
||||
<span><strong className="text-[var(--text-secondary)]">Download from website:</strong> Visit the app's official site and grab the <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code>, <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code>, or <code className="bg-[var(--bg-tertiary)] px-1 rounded">.AppImage</code></span>
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>
|
||||
<strong className="text-[var(--text-secondary)]">Running the script:</strong>{' '}
|
||||
<code className="bg-[var(--bg-tertiary)] px-1 rounded">chmod +x tuxmate-*.sh && ./tuxmate-*.sh</code> or{' '}
|
||||
<code className="bg-[var(--bg-tertiary)] px-1 rounded">bash tuxmate-*.sh</code>
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-[var(--accent)]">•</span>
|
||||
<span><strong className="text-[var(--text-secondary)]">Check GitHub Releases:</strong> Many apps publish packages on their GitHub releases page</span>
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>Your selections are <strong className="text-[var(--text-secondary)]">saved automatically</strong> — come back anytime to modify your setup</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-[var(--accent)]">•</span>
|
||||
<span><strong className="text-[var(--text-secondary)]">Hover the ⓘ icon:</strong> Some unavailable apps show links to alternative download methods</span>
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dpkg -i file.deb</code></span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dnf install ./file.rpm</code></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arch & AUR */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Arch Linux & AUR</h4>
|
||||
<p className="text-xs text-[var(--text-muted)] leading-relaxed">
|
||||
Some Arch packages are in the <strong className="text-[var(--text-secondary)]">AUR</strong> (Arch User Repository).
|
||||
TuxMate uses <code className="bg-[var(--bg-tertiary)] px-1 rounded">yay</code> to install these.
|
||||
If you don't have yay, check "I have yay installed" to skip auto-installation, or leave it unchecked to install yay first.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Keyboard Shortcuts */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Keyboard Shortcuts</h4>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">↑↓</kbd>
|
||||
<span className="text-[var(--text-muted)]">Navigate apps</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Space</kbd>
|
||||
<span className="text-[var(--text-muted)]">Toggle selection</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Enter</kbd>
|
||||
<span className="text-[var(--text-muted)]">Expand/collapse</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="px-1.5 py-0.5 bg-[var(--bg-tertiary)] text-[var(--text-secondary)] rounded text-[10px] font-mono">Esc</kbd>
|
||||
<span className="text-[var(--text-muted)]">Close popups</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pro Tips */}
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
<h4 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-3">Pro Tips</h4>
|
||||
<ul className="space-y-2 text-xs text-[var(--text-muted)] leading-relaxed">
|
||||
<li className="flex gap-2">
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>The <strong className="text-[var(--text-secondary)]">download button</strong> gives you a full shell script with progress tracking, error handling, and a summary</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>Your selections are <strong className="text-[var(--text-secondary)]">saved automatically</strong> — come back anytime to modify your setup</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.deb</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dpkg -i file.deb</code> or double-click in your file manager</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-emerald-500">💡</span>
|
||||
<span>Running <code className="bg-[var(--bg-tertiary)] px-1 rounded">.rpm</code> files: <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo dnf install ./file.rpm</code> or <code className="bg-[var(--bg-tertiary)] px-1 rounded">sudo zypper install ./file.rpm</code></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow pointer */}
|
||||
<div
|
||||
className="absolute w-3 h-3 bg-[var(--bg-secondary)] border-l border-t border-[var(--border-primary)] rotate-45"
|
||||
style={{ top: '-7px', left: '24px' }}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={triggerRef}
|
||||
onClick={() => {
|
||||
const wasOpen = isOpen;
|
||||
setIsOpen(!isOpen);
|
||||
if (!wasOpen) analytics.helpOpened();
|
||||
else analytics.helpClosed();
|
||||
}}
|
||||
onClick={handleOpen}
|
||||
className={`flex items-center gap-1.5 text-sm transition-all duration-200 hover:scale-105 ${isOpen ? 'text-[var(--text-primary)]' : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)]'}`}
|
||||
>
|
||||
<HelpCircle className="w-4 h-4" />
|
||||
<span className="hidden sm:inline whitespace-nowrap">How it works?</span>
|
||||
</button>
|
||||
{mounted && typeof document !== 'undefined' && createPortal(popup, document.body)}
|
||||
{isOpen && mounted && typeof document !== 'undefined' && createPortal(modal, document.body)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user