The Sidebar component provides the file management interface, displaying a list of open files and actions for uploading, creating, and removing files.
Props
Controls sidebar visibility. When false, the sidebar is hidden.
Array of file objects. Each file has id, name, and content properties.
ID of the currently selected file. Used to highlight the active file in the list.
Callback function called when a file is clicked. Receives the file ID as argument.
Callback function called when the new file button is clicked.
Callback function called when a file’s remove button is clicked. Receives file ID and event object.
Callback function called when files are uploaded. Receives an array of File objects.
File upload
The component uses a hidden file input for uploading markdown files:
const fileInputRef = useRef(null);
const handleClickUpload = () => {
fileInputRef.current?.click();
};
const handleFileChange = (e) => {
const fileList = Array.from(e.target.files);
if (fileList.length > 0) {
onFileUpload(fileList);
}
// Reset the input so the same file(s) can be uploaded again if needed
e.target.value = null;
};
The input accepts markdown and text files with multiple file selection:
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept=".md,.markdown,text/markdown,text/plain"
multiple
style={{ display: 'none' }}
/>
File list rendering
When files are present, they’re rendered as clickable items:
files.map(file => (
<div
key={file.id}
className={`file-item ${file.id === activeFileId ? 'active' : ''}`}
onClick={() => onSelectFile(file.id)}
>
<FileText size={16} style={{ flexShrink: 0 }} />
<span className="file-name" style={{
flex: 1,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{file.name}
</span>
<button
className="btn-icon file-remove-btn"
onClick={(e) => onRemoveFile(file.id, e)}
title="Remove file"
>
<X size={14} />
</button>
</div>
))
Active file highlighting
The active file is highlighted using conditional class application:
className={`file-item ${file.id === activeFileId ? 'active' : ''}`}
The active class applies distinct styling to indicate the current selection.
Empty state
When no files are open, the sidebar displays an empty state:
{files.length === 0 ? (
<div style={{
textAlign: 'center',
padding: 'var(--space-md)',
color: 'var(--text-secondary)',
fontSize: '0.85rem'
}}>
<Ghost size={24} style={{ marginBottom: '8px', opacity: 0.5 }} />
<p>No files open</p>
</div>
) : (
// file list rendering
)}
The sidebar header contains two action buttons:
<div className="sidebar-header">
<span>Files</span>
<div style={{ flex: 1 }} />
<button className="btn-icon" onClick={handleClickUpload} title="Open Markdown File">
<Upload size={16} />
</button>
<button className="btn-icon" onClick={onNewFile} title="New empty file">
<Plus size={16} />
</button>
</div>
Usage example
<Sidebar
isOpen={isOpen}
files={files}
activeFileId={activeFileId}
onSelectFile={setActiveFileId}
onNewFile={handleNewFile}
onRemoveFile={handleRemoveFile}
onFileUpload={loadFile}
/>