feat: redesign backing track player UI to match design
Redesigned UI to match cleaner, more minimal design from mockups:
Visual changes:
- Circular icon buttons (play and stop) instead of rectangular
- Removed volume control slider (not in design)
- Cleaner title formatting ("Backing Track: filename")
- Improved seek bar with progress gradient
- Monospace font for time display
- Simplified loop checkbox styling
- Clean "Close" button styling
Technical changes:
- Removed unused volume state and handlers
- Removed unused imports (Button, volume icons, mixers selector)
- Consistent styling between popup and modal versions
- Updated button states (play = blue, pause = gray, stop = gray)
- Better spacing and layout
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
abf6ba7105
commit
197d91e9af
|
|
@ -1,10 +1,8 @@
|
|||
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, FormGroup, Label, Input } from 'reactstrap';
|
||||
import { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPlay, faPause, faStop, faVolumeUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faPlay, faPause, faStop } from '@fortawesome/free-solid-svg-icons';
|
||||
import useMediaActions from '../../hooks/useMediaActions';
|
||||
import { selectBackingTrackMixers } from '../../store/features/mixersSlice';
|
||||
|
||||
const JKSessionBackingTrackPlayer = ({
|
||||
isOpen,
|
||||
|
|
@ -16,11 +14,9 @@ const JKSessionBackingTrackPlayer = ({
|
|||
isPopup = false
|
||||
}) => {
|
||||
const { closeMedia } = useMediaActions();
|
||||
const backingTrackMixers = useSelector(selectBackingTrackMixers);
|
||||
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isLooping, setIsLooping] = useState(false);
|
||||
const [volume, setVolume] = useState(100);
|
||||
const [currentTime, setCurrentTime] = useState('0:00');
|
||||
const [duration, setDuration] = useState('0:00');
|
||||
const [currentPositionMs, setCurrentPositionMs] = useState(0);
|
||||
|
|
@ -37,16 +33,6 @@ const JKSessionBackingTrackPlayer = ({
|
|||
// Visibility tracking for performance optimization
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
const volumeRef = useRef(null);
|
||||
const trackVolumeObjectRef = useRef({
|
||||
volL: 0,
|
||||
volR: 0,
|
||||
pan: 0,
|
||||
mute: false,
|
||||
solo: false,
|
||||
loop: false
|
||||
});
|
||||
|
||||
// UAT-003 Investigation: Track seek attempts while paused
|
||||
const pendingSeekPositionRef = useRef(null);
|
||||
|
||||
|
|
@ -358,62 +344,6 @@ const JKSessionBackingTrackPlayer = ({
|
|||
}
|
||||
}, [isOperating, jamClient, handleError, clearError]);
|
||||
|
||||
const handleVolumeChange = useCallback(async (e) => {
|
||||
const newVolume = parseInt(e.target.value);
|
||||
const previousVolume = volume;
|
||||
setVolume(newVolume);
|
||||
|
||||
try {
|
||||
if (!jamClient) {
|
||||
handleError('general', 'Audio engine not available', null);
|
||||
setVolume(previousVolume); // Revert to previous
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[VOL] Volume change requested:', newVolume, 'Mixers:', backingTrackMixers);
|
||||
|
||||
// Volume is controlled through the mixer system
|
||||
// Get the backing track mixer (use personal mixer)
|
||||
const backingTrackMixer = backingTrackMixers[0]?.mixers?.personal?.mixer;
|
||||
|
||||
console.log('[VOL] Backing track mixer:', backingTrackMixer);
|
||||
|
||||
if (!backingTrackMixer) {
|
||||
console.warn('[VOL] No backing track mixer found, volume control unavailable');
|
||||
// No mixer available, silently skip
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert 0-100 to dB range (-80 to +20)
|
||||
// Using FaderHelpers conversion formula
|
||||
const minDb = -80;
|
||||
const maxDb = 20;
|
||||
const volumeDb = minDb + (newVolume / 100) * (maxDb - minDb);
|
||||
|
||||
console.log('[VOL] Setting volume to', volumeDb, 'dB');
|
||||
|
||||
// Update track volume object
|
||||
trackVolumeObjectRef.current = {
|
||||
...trackVolumeObjectRef.current,
|
||||
volL: volumeDb,
|
||||
volR: volumeDb
|
||||
};
|
||||
|
||||
// Set volume through mixer system
|
||||
await jamClient.SessionSetTrackVolumeData(
|
||||
backingTrackMixer.id,
|
||||
backingTrackMixer.mode,
|
||||
trackVolumeObjectRef.current
|
||||
);
|
||||
|
||||
console.log('[VOL] Volume set successfully');
|
||||
} catch (error) {
|
||||
console.error('[VOL] Volume change failed:', error);
|
||||
handleError('playback', 'Failed to adjust volume', error);
|
||||
setVolume(previousVolume); // Revert to previous on error
|
||||
}
|
||||
}, [volume, jamClient, backingTrackMixers, handleError]);
|
||||
|
||||
const handleLoopChange = useCallback(async (e) => {
|
||||
const shouldLoop = e.target.checked;
|
||||
const previousLoop = isLooping;
|
||||
|
|
@ -568,19 +498,23 @@ const JKSessionBackingTrackPlayer = ({
|
|||
{/* Popup Content */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
padding: '20px',
|
||||
padding: '30px 20px',
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<div style={{ maxWidth: '400px', width: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||
{/* Row 1: Backing Track */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', padding: '12px', backgroundColor: '#e9ecef', borderRadius: '4px' }}>
|
||||
<strong style={{ marginRight: '8px' }}>Backing Track:</strong>
|
||||
<span>{getFileName(backingTrack)}</span>
|
||||
<div style={{ maxWidth: '420px', width: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
||||
{/* Title */}
|
||||
<div style={{
|
||||
color: '#666',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'normal'
|
||||
}}>
|
||||
<span style={{ color: '#999' }}>Backing Track: </span>
|
||||
{getFileName(backingTrack)}
|
||||
</div>
|
||||
|
||||
{/* Error Display */}
|
||||
|
|
@ -616,79 +550,118 @@ const JKSessionBackingTrackPlayer = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Row 2: Controls */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px' }}>
|
||||
<Button
|
||||
color="primary"
|
||||
{/* Controls Section */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||
{/* Circular Buttons and Seek Bar */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||
{/* Play Button - Circular */}
|
||||
<button
|
||||
onClick={handlePlay}
|
||||
disabled={!backingTrack || isLoadingDuration || isOperating || error}
|
||||
style={{
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
borderRadius: '50%',
|
||||
border: 'none',
|
||||
backgroundColor: isPlaying ? '#6c757d' : '#5b9bd5',
|
||||
color: 'white',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: (!backingTrack || isLoadingDuration || isOperating || error) ? 'not-allowed' : 'pointer',
|
||||
opacity: (!backingTrack || isLoadingDuration || isOperating || error) ? 0.5 : 1,
|
||||
outline: 'none',
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isPlaying ? faPause : faPlay} />
|
||||
{isOperating && ' ...'}
|
||||
</Button>
|
||||
<FontAwesomeIcon icon={isPlaying ? faPause : faPlay} style={{ fontSize: '14px' }} />
|
||||
</button>
|
||||
|
||||
<Button
|
||||
color="secondary"
|
||||
{/* Stop Button - Circular */}
|
||||
<button
|
||||
onClick={handleStop}
|
||||
disabled={!backingTrack || isLoadingDuration || isOperating}
|
||||
style={{
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
borderRadius: '50%',
|
||||
border: 'none',
|
||||
backgroundColor: '#b0b0b0',
|
||||
color: 'white',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: (!backingTrack || isLoadingDuration || isOperating) ? 'not-allowed' : 'pointer',
|
||||
opacity: (!backingTrack || isLoadingDuration || isOperating) ? 0.5 : 1,
|
||||
outline: 'none',
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faStop} />
|
||||
</Button>
|
||||
<FontAwesomeIcon icon={faStop} style={{ fontSize: '12px' }} />
|
||||
</button>
|
||||
|
||||
{/* Time and Seek Bar */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', flex: 1, gap: '8px' }}>
|
||||
<span style={{ fontSize: '14px', color: '#666', minWidth: '35px', fontFamily: 'monospace' }}>{currentTime}</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={durationMs || 100}
|
||||
value={currentPositionMs}
|
||||
onChange={handleSeek}
|
||||
disabled={!backingTrack || isLoadingDuration}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: '4px',
|
||||
borderRadius: '2px',
|
||||
outline: 'none',
|
||||
background: `linear-gradient(to right, #5b9bd5 0%, #5b9bd5 ${(currentPositionMs / (durationMs || 1)) * 100}%, #ddd ${(currentPositionMs / (durationMs || 1)) * 100}%, #ddd 100%)`,
|
||||
WebkitAppearance: 'none',
|
||||
cursor: (!backingTrack || isLoadingDuration) ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
/>
|
||||
<span style={{ fontSize: '14px', color: '#666', minWidth: '35px', fontFamily: 'monospace' }}>{duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Seek bar with duration */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', width: '100%', gap: '8px' }}>
|
||||
<span style={{ fontSize: '14px', minWidth: '40px' }}>{currentTime}</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={durationMs || 100}
|
||||
value={currentPositionMs}
|
||||
onChange={handleSeek}
|
||||
style={{ flex: 1 }}
|
||||
disabled={!backingTrack || isLoadingDuration}
|
||||
/>
|
||||
<span style={{ fontSize: '14px', minWidth: '40px' }}>{duration}</span>
|
||||
{/* Loop Checkbox */}
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-start', paddingLeft: '0px' }}>
|
||||
<label style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
color: '#666',
|
||||
userSelect: 'none'
|
||||
}}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isLooping}
|
||||
onChange={handleLoopChange}
|
||||
style={{ marginRight: '8px', cursor: 'pointer' }}
|
||||
/>
|
||||
Loop playback
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 3: Loop Checkbox */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '8px' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isLooping}
|
||||
onChange={handleLoopChange}
|
||||
style={{ marginRight: '8px' }}
|
||||
/>
|
||||
Loop playback
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Row 4: Volume Control */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '8px' }}>
|
||||
<FontAwesomeIcon icon={faVolumeUp} />
|
||||
<input
|
||||
ref={volumeRef}
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={volume}
|
||||
onChange={handleVolumeChange}
|
||||
style={{ width: '200px' }}
|
||||
/>
|
||||
<span style={{ fontWeight: 'bold', minWidth: '40px' }}>{volume}%</span>
|
||||
</div>
|
||||
|
||||
{/* Close Button */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '16px' }}>
|
||||
<Button
|
||||
color="secondary"
|
||||
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '8px' }}>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
style={{
|
||||
padding: '8px 24px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'white',
|
||||
color: '#666',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
outline: 'none'
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -705,11 +678,15 @@ const JKSessionBackingTrackPlayer = ({
|
|||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||
{/* Row 1: Backing Track */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', padding: '8px', backgroundColor: '#f8f9fa', borderRadius: '4px' }}>
|
||||
<strong style={{ marginRight: '8px' }}>Backing Track:</strong>
|
||||
<span>{getFileName(backingTrack)}</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
||||
{/* Title */}
|
||||
<div style={{
|
||||
color: '#666',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'normal'
|
||||
}}>
|
||||
<span style={{ color: '#999' }}>Backing Track: </span>
|
||||
{getFileName(backingTrack)}
|
||||
</div>
|
||||
|
||||
{/* Error Display */}
|
||||
|
|
@ -726,13 +703,34 @@ const JKSessionBackingTrackPlayer = ({
|
|||
}}>
|
||||
<div>{error}</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<Button size="sm" color="secondary" onClick={clearError}>
|
||||
<button
|
||||
onClick={clearError}
|
||||
style={{
|
||||
padding: '4px 12px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'white',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px'
|
||||
}}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
</button>
|
||||
{(errorType === 'network' || errorType === 'file') && (
|
||||
<Button size="sm" color="primary" onClick={retryOperation}>
|
||||
<button
|
||||
onClick={retryOperation}
|
||||
style={{
|
||||
padding: '4px 12px',
|
||||
border: '1px solid #5b9bd5',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#5b9bd5',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px'
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -745,77 +743,119 @@ const JKSessionBackingTrackPlayer = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Row 2: Controls */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px' }}>
|
||||
<Button
|
||||
color="primary"
|
||||
{/* Controls Section */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||
{/* Circular Buttons and Seek Bar */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||
{/* Play Button - Circular */}
|
||||
<button
|
||||
onClick={handlePlay}
|
||||
disabled={!backingTrack || isLoadingDuration || isOperating || error}
|
||||
style={{
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
borderRadius: '50%',
|
||||
border: 'none',
|
||||
backgroundColor: isPlaying ? '#6c757d' : '#5b9bd5',
|
||||
color: 'white',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: (!backingTrack || isLoadingDuration || isOperating || error) ? 'not-allowed' : 'pointer',
|
||||
opacity: (!backingTrack || isLoadingDuration || isOperating || error) ? 0.5 : 1,
|
||||
outline: 'none',
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isPlaying ? faPause : faPlay} /> Play/Pause
|
||||
{isOperating && ' ...'}
|
||||
</Button>
|
||||
<FontAwesomeIcon icon={isPlaying ? faPause : faPlay} style={{ fontSize: '14px' }} />
|
||||
</button>
|
||||
|
||||
<Button
|
||||
color="secondary"
|
||||
{/* Stop Button - Circular */}
|
||||
<button
|
||||
onClick={handleStop}
|
||||
disabled={!backingTrack || isLoadingDuration || isOperating}
|
||||
style={{
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
borderRadius: '50%',
|
||||
border: 'none',
|
||||
backgroundColor: '#b0b0b0',
|
||||
color: 'white',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: (!backingTrack || isLoadingDuration || isOperating) ? 'not-allowed' : 'pointer',
|
||||
opacity: (!backingTrack || isLoadingDuration || isOperating) ? 0.5 : 1,
|
||||
outline: 'none',
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faStop} /> Stop
|
||||
</Button>
|
||||
<FontAwesomeIcon icon={faStop} style={{ fontSize: '12px' }} />
|
||||
</button>
|
||||
|
||||
{/* Time and Seek Bar */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', flex: 1, gap: '8px' }}>
|
||||
<span style={{ fontSize: '14px', color: '#666', minWidth: '35px', fontFamily: 'monospace' }}>{currentTime}</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={durationMs || 100}
|
||||
value={currentPositionMs}
|
||||
onChange={handleSeek}
|
||||
disabled={!backingTrack || isLoadingDuration}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: '4px',
|
||||
borderRadius: '2px',
|
||||
outline: 'none',
|
||||
background: `linear-gradient(to right, #5b9bd5 0%, #5b9bd5 ${(currentPositionMs / (durationMs || 1)) * 100}%, #ddd ${(currentPositionMs / (durationMs || 1)) * 100}%, #ddd 100%)`,
|
||||
WebkitAppearance: 'none',
|
||||
cursor: (!backingTrack || isLoadingDuration) ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
/>
|
||||
<span style={{ fontSize: '14px', color: '#666', minWidth: '35px', fontFamily: 'monospace' }}>{duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Seek bar with duration */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', width: '100%', gap: '8px' }}>
|
||||
<span style={{ fontSize: '14px', minWidth: '40px' }}>{currentTime}</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max={durationMs || 100}
|
||||
value={currentPositionMs}
|
||||
onChange={handleSeek}
|
||||
style={{ flex: 1 }}
|
||||
disabled={!backingTrack || isLoadingDuration}
|
||||
/>
|
||||
<span style={{ fontSize: '14px', minWidth: '40px' }}>{duration}</span>
|
||||
{/* Loop Checkbox */}
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-start', paddingLeft: '0px' }}>
|
||||
<label style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
color: '#666',
|
||||
userSelect: 'none'
|
||||
}}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isLooping}
|
||||
onChange={handleLoopChange}
|
||||
style={{ marginRight: '8px', cursor: 'pointer' }}
|
||||
/>
|
||||
Loop playback
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 3: Loop Checkbox */}
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '8px' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isLooping}
|
||||
onChange={handleLoopChange}
|
||||
style={{ marginRight: '8px' }}
|
||||
/>
|
||||
Loop playback
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Row 4: Volume Control */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '8px' }}>
|
||||
<FontAwesomeIcon icon={faVolumeUp} />
|
||||
<input
|
||||
ref={volumeRef}
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={volume}
|
||||
onChange={handleVolumeChange}
|
||||
style={{ width: '200px' }}
|
||||
/>
|
||||
<span style={{ fontWeight: 'bold', minWidth: '40px' }}>{volume}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={onClose}>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
padding: '8px 24px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'white',
|
||||
color: '#666',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
outline: 'none'
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue