Skip to content

Commit 35ae36b

Browse files
author
Sqoia Dev Agent
committed
fix: modals now visible + wizard steps switch correctly
Modal fix (root cause: PatternFly collision + overflow clipping): - PatternFly ships .modal with display:none — our class merged with it - Renamed to .sl-modal-overlay and .sl-modal to avoid conflict - Added ModalPortal using ReactDOM.createPortal to render modals at document.body level, outside any scrollable/overflow container - All three modals (Allocation, BillingRule, Refund) now portal to body Wizard fix (root cause: Fragment children spreading in ternary): - Extracted renderStep() function — each step returns a plain div - Only one step renders per call, no Fragment spreading issues - Key={step} forces React to unmount/remount on step change - Steps now properly replace each other instead of stacking
1 parent 7c8c75c commit 35ae36b

2 files changed

Lines changed: 75 additions & 50 deletions

File tree

src/slurmledger.css

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -282,23 +282,24 @@ nav button:hover {
282282
margin-top: 0.2em;
283283
}
284284

285-
/* Modal overlay for refunds and other dialogs */
286-
.modal-overlay {
285+
/* Modal overlay for refunds and other dialogs.
286+
Prefixed sl- to avoid collisions with PatternFly's own .modal class.
287+
Both elements are portalled to document.body so overflow/stacking-context
288+
on ancestor containers cannot clip or hide them. */
289+
.sl-modal-overlay {
287290
position: fixed;
288291
top: 0;
289292
left: 0;
290-
right: 0;
291-
bottom: 0;
292-
width: 100vw;
293-
height: 100vh;
293+
width: 100%;
294+
height: 100%;
294295
background: rgba(0, 0, 0, 0.5);
295296
display: flex;
296297
align-items: center;
297298
justify-content: center;
298-
z-index: 9999;
299+
z-index: 99999;
299300
}
300301

301-
.modal {
302+
.sl-modal {
302303
background: #fff;
303304
border-radius: 8px;
304305
padding: 1.5em 2em;
@@ -307,11 +308,11 @@ nav button:hover {
307308
max-width: 600px;
308309
max-height: 80vh;
309310
overflow-y: auto;
310-
z-index: 10000;
311+
z-index: 100000;
311312
position: relative;
312313
}
313314

314-
.modal h3 {
315+
.sl-modal h3 {
315316
margin-top: 0;
316317
margin-bottom: 1em;
317318
}

src/slurmledger.js

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,12 @@ function AllocationProgressBar({ percent }) {
20252025
);
20262026
}
20272027

2028+
// Renders modal content as a direct child of document.body, escaping any
2029+
// overflow/stacking-context created by ancestor scrollable containers.
2030+
function ModalPortal({ children }) {
2031+
return ReactDOM.createPortal(children, document.body);
2032+
}
2033+
20282034
// Modal for editing a single allocation entry
20292035
function AllocationModal({ allocation, accountName, onSave, onClose }) {
20302036
const [form, setForm] = useState(allocation ? { ...allocation } : {
@@ -2072,18 +2078,21 @@ function AllocationModal({ allocation, accountName, onSave, onClose }) {
20722078
}
20732079

20742080
return React.createElement(
2075-
'div',
2076-
{ className: 'modal-overlay', onClick: onClose, onKeyDown: (e) => { if (e.key === 'Escape') onClose(); } },
2081+
ModalPortal,
2082+
null,
20772083
React.createElement(
20782084
'div',
2079-
{
2080-
className: 'modal',
2081-
onClick: e => e.stopPropagation(),
2082-
style: { maxWidth: '520px', width: '95%' }
2083-
},
2084-
React.createElement('h3', null, `${allocation ? 'Edit' : 'Add'} Allocation: ${accountName}`),
2085-
error && React.createElement('p', { className: 'error' }, error),
2085+
{ className: 'sl-modal-overlay', onClick: onClose, onKeyDown: (e) => { if (e.key === 'Escape') onClose(); } },
20862086
React.createElement(
2087+
'div',
2088+
{
2089+
className: 'sl-modal',
2090+
onClick: e => e.stopPropagation(),
2091+
style: { maxWidth: '520px', width: '95%' }
2092+
},
2093+
React.createElement('h3', null, `${allocation ? 'Edit' : 'Add'} Allocation: ${accountName}`),
2094+
error && React.createElement('p', { className: 'error' }, error),
2095+
React.createElement(
20872096
'div',
20882097
{ style: { marginBottom: '0.75em' } },
20892098
React.createElement('label', null, 'Type: '),
@@ -2180,6 +2189,7 @@ function AllocationModal({ allocation, accountName, onSave, onClose }) {
21802189
React.createElement('button', { onClick: handleSave }, 'Save')
21812190
)
21822191
)
2192+
)
21832193
);
21842194
}
21852195

@@ -2719,14 +2729,17 @@ function BillingRuleModal({ rule, onSave, onClose }) {
27192729
: '';
27202730

27212731
return React.createElement(
2722-
'div',
2723-
{ className: 'modal-overlay', onClick: onClose, onKeyDown: (e) => { if (e.key === 'Escape') onClose(); } },
2732+
ModalPortal,
2733+
null,
27242734
React.createElement(
27252735
'div',
2726-
{ className: 'modal', onClick: e => e.stopPropagation(), style: { maxWidth: '560px', width: '95%' } },
2727-
React.createElement('h3', null, isNew ? 'Add Billing Rule' : `Edit Rule: ${rule.name}`),
2728-
error && React.createElement('p', { className: 'error' }, error),
2729-
React.createElement('div', { style: { marginBottom: '0.75em' } },
2736+
{ className: 'sl-modal-overlay', onClick: onClose, onKeyDown: (e) => { if (e.key === 'Escape') onClose(); } },
2737+
React.createElement(
2738+
'div',
2739+
{ className: 'sl-modal', onClick: e => e.stopPropagation(), style: { maxWidth: '560px', width: '95%' } },
2740+
React.createElement('h3', null, isNew ? 'Add Billing Rule' : `Edit Rule: ${rule.name}`),
2741+
error && React.createElement('p', { className: 'error' }, error),
2742+
React.createElement('div', { style: { marginBottom: '0.75em' } },
27302743
React.createElement('label', null, 'Rule ID: '),
27312744
React.createElement('input', {
27322745
ref: firstInputRef, type: 'text', value: form.id, disabled: !isNew,
@@ -2830,6 +2843,7 @@ function BillingRuleModal({ rule, onSave, onClose }) {
28302843
React.createElement('button', { onClick: handleSave }, 'Save')
28312844
)
28322845
)
2846+
)
28332847
);
28342848
}
28352849

@@ -4329,16 +4343,19 @@ function RefundModal({ invoice, currentUser, onClose, onIssue }) {
43294343
}
43304344

43314345
return React.createElement(
4332-
'div',
4333-
{ className: 'modal-overlay', onClick: onClose, onKeyDown: (e) => { if (e.key === 'Escape') onClose(); } },
4346+
ModalPortal,
4347+
null,
43344348
React.createElement(
43354349
'div',
4336-
{
4337-
className: 'modal',
4338-
onClick: e => e.stopPropagation(),
4339-
style: { maxWidth: '480px', width: '90%' }
4340-
},
4341-
React.createElement('h3', null, `Issue Refund — ${invoice.id}`),
4350+
{ className: 'sl-modal-overlay', onClick: onClose, onKeyDown: (e) => { if (e.key === 'Escape') onClose(); } },
4351+
React.createElement(
4352+
'div',
4353+
{
4354+
className: 'sl-modal',
4355+
onClick: e => e.stopPropagation(),
4356+
style: { maxWidth: '480px', width: '90%' }
4357+
},
4358+
React.createElement('h3', null, `Issue Refund — ${invoice.id}`),
43424359
React.createElement(
43434360
'div',
43444361
{ style: { marginBottom: '0.75em' } },
@@ -4391,6 +4408,7 @@ function RefundModal({ invoice, currentUser, onClose, onIssue }) {
43914408
)
43924409
)
43934410
)
4411+
)
43944412
);
43954413
}
43964414

@@ -6020,14 +6038,19 @@ function SetupWizard({ onComplete }) {
60206038
),
60216039
React.createElement('div', { style: { flexShrink: 0 } }, progressBar),
60226040

6023-
// Step content — single scrollable container that switches content by step
6041+
// Step content — keyed wrapper remounts on step change; renderStep returns
6042+
// a single plain div per step so there is no Fragment ambiguity.
60246043
React.createElement(
60256044
'div',
6026-
{ key: 'step-' + step, style: { overflowY: 'auto', flex: 1, paddingRight: '0.5em' } },
6045+
{ key: step, style: { overflowY: 'auto', flex: 1, paddingRight: '0.5em' } },
6046+
renderStep()
6047+
)
6048+
); // close SetupWizard outer div
60276049

6028-
// Step 1: Institution Profile
6029-
step === 1 ? React.createElement(
6030-
React.Fragment,
6050+
function renderStep() {
6051+
if (step === 1) {
6052+
return React.createElement(
6053+
'div',
60316054
null,
60326055
React.createElement(InstitutionProfile, null),
60336056
React.createElement(
@@ -6039,11 +6062,12 @@ function SetupWizard({ onComplete }) {
60396062
'Next: Set Billing Rates \u2192'
60406063
)
60416064
)
6042-
) :
6065+
);
6066+
}
60436067

6044-
// Step 2: Rate Configuration
6045-
step === 2 ? React.createElement(
6046-
React.Fragment,
6068+
if (step === 2) {
6069+
return React.createElement(
6070+
'div',
60476071
null,
60486072
React.createElement(Rates, { onRatesUpdated: () => {}, username: '', userRole: 'admin' }),
60496073
React.createElement(
@@ -6056,12 +6080,13 @@ function SetupWizard({ onComplete }) {
60566080
'Next: Test Connection \u2192'
60576081
)
60586082
)
6059-
) :
6083+
);
6084+
}
60606085

6061-
// Step 3: Database connection test
6062-
React.createElement(
6063-
React.Fragment,
6064-
null,
6086+
// Step 3: Database connection test
6087+
return React.createElement(
6088+
'div',
6089+
null,
60656090
React.createElement('h3', null, 'Test Database Connection'),
60666091
React.createElement(
60676092
'p',
@@ -6131,9 +6156,8 @@ function SetupWizard({ onComplete }) {
61316156
)
61326157
)
61336158
)
6134-
) // close Step 3 Fragment
6135-
) // close scrollable wrapper div
6136-
); // close SetupWizard outer div
6159+
);
6160+
}
61376161
}
61386162

61396163
function App() {

0 commit comments

Comments
 (0)