Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 50 additions & 7 deletions src/components/ActionBar/ActionBar.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { useSelection } from '../../context/SelectionContext';
import { useScriptGenerator } from '../../hooks/useScriptGenerator';
import { useState } from 'react';
import WinDialog from '../Common/WinDialog';

const ActionBar = () => {
const { selectedSoftware, selectedConfigs, clearAll } = useSelection();
const { downloadScript } = useScriptGenerator();
const [showClearDialog, setShowClearDialog] = useState(false);
const [showDownloadDialog, setShowDownloadDialog] = useState(false);

const totalSelected = selectedSoftware.length + selectedConfigs.length;

const handleDownload = () => {
downloadScript();
if (totalSelected === 0) return;
setShowDownloadDialog(true);
};

const handleClear = () => {
if (
totalSelected > 0 &&
confirm(`Clear all ${totalSelected} selections?`)
) {
clearAll();
}
if (totalSelected === 0) return;
setShowClearDialog(true);
};

return (
Expand Down Expand Up @@ -60,6 +61,48 @@ const ActionBar = () => {
</div>
</div>
</div>

<WinDialog
open={showDownloadDialog}
title="Download Script"
icon="📄"
onClose={() => setShowDownloadDialog(false)}
primaryAction={{
label: 'Download',
onClick: () => {
downloadScript();
setShowDownloadDialog(false);
},
variant: 'primary',
}}
secondaryAction={{
label: 'Close',
onClick: () => setShowDownloadDialog(false),
}}
>
Right-click the downloaded .bat file and select "Run as Administrator"
</WinDialog>

<WinDialog
open={showClearDialog}
title="Clear All"
icon="⚠️"
onClose={() => setShowClearDialog(false)}
primaryAction={{
label: 'Clear',
onClick: () => {
clearAll();
setShowClearDialog(false);
},
variant: 'danger',
}}
secondaryAction={{
label: 'Cancel',
onClick: () => setShowClearDialog(false),
}}
>
Clear all {totalSelected} selections?
</WinDialog>
</div>
);
};
Expand Down
110 changes: 110 additions & 0 deletions src/components/Common/WinDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useEffect, useId, useRef } from 'react';

const WinDialog = ({
open,
title,
children,
primaryAction,
secondaryAction,
onClose,
icon = 'ℹ️',
width = 420,
}) => {
const titleId = useId();
const dialogRef = useRef(null);
const primaryButtonRef = useRef(null);

useEffect(() => {
if (!open) return;

const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';

const focusTimer = window.setTimeout(() => {
if (primaryButtonRef.current) {
primaryButtonRef.current.focus();
} else if (dialogRef.current) {
dialogRef.current.focus();
}
}, 0);

const handleKeyDown = (e) => {
if (e.key === 'Escape') onClose?.();
};
window.addEventListener('keydown', handleKeyDown);

return () => {
window.clearTimeout(focusTimer);
window.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = previousOverflow;
};
}, [open, onClose]);

if (!open) return null;

return (
<div
className="win-dialog-overlay"
role="presentation"
onMouseDown={(e) => {
if (e.target === e.currentTarget) onClose?.();
}}
>
<div
className="xp-window win-dialog"
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
ref={dialogRef}
tabIndex={-1}
style={{ width }}
>
<div className="xp-title-bar">
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span style={{ fontSize: '16px' }}>{icon}</span>
<span id={titleId}>{title}</span>
</div>
<div className="xp-title-buttons">
<button
className="xp-title-button xp-title-button-close"
title="Close"
onClick={() => onClose?.()}
aria-label="Close dialog"
>
</button>
</div>
</div>

<div style={{ padding: '12px' }}>
<div style={{ marginBottom: '12px', fontSize: '11px', lineHeight: 1.4 }}>
{children}
</div>

<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '6px', flexWrap: 'wrap' }}>
{secondaryAction && (
<button
className="win98-button"
onClick={secondaryAction.onClick}
>
{secondaryAction.label}
</button>
)}
{primaryAction && (
<button
ref={primaryButtonRef}
className={`win98-button ${primaryAction.variant === 'danger' ? 'win98-button-danger' : 'win98-button-primary'}`}
onClick={primaryAction.onClick}
>
{primaryAction.label}
</button>
)}
</div>
</div>
</div>
</div>
);
};

export default WinDialog;

2 changes: 0 additions & 2 deletions src/components/Layout/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ const Header = () => {
• automatically installs selected programs<br />
• adjusts selected settings<br />
Everything automatically<br />
<br />
Right-click the downloaded .bat file and select "Run as Administrator"
</p>
</div>
<button
Expand Down
27 changes: 27 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,30 @@ body.dark {
.dark ::-webkit-scrollbar-button:vertical:increment {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M4 6L1 2h6z' fill='%23e0e0e0'/%3E%3C/svg%3E");
}

/* ================================================== */
/* WIN-STYLE DIALOGS */
/* ================================================== */

.win-dialog-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
z-index: 9999;
}

.win-dialog {
max-width: calc(100vw - 32px);
}

/* Match button sizing inside dialogs */
.win-dialog .win98-button {
height: auto;
min-height: 32px;
padding: 6px 12px;
font-size: 12px;
}