Modal System Guide
Overview
Hibiscus v0.7.0 introduces a production-grade modal system that replaces browser-native dialogs with custom React components designed specifically for desktop applications.
Design Philosophy
The modal system follows a hybrid design approach:
- Clean & Minimal: Distraction-free interface with soft shadows and rounded corners
- Responsive Design: Adapts to different screen sizes while maintaining usability
- Theme Integration: Uses existing CSS variables for consistent theming
- Accessibility First: Proper ARIA attributes, keyboard navigation, and focus management
Components
NewItemModal
Location: src/components/Modals/NewItemModal.tsx
The primary modal component for creating new files and folders.
Props
interface NewItemModalProps {
open: boolean // Whether modal is visible
mode: "file" | "folder" // Creation mode
onClose: () => void // Close callback
onCreate: (name: string) => Promise<void> // Creation callback
defaultPath?: string // Optional path hint
existingNames?: string[] // Names to prevent duplicates
}
Features
- Input Validation: Prevents invalid characters and duplicate names
- Keyboard Navigation: Enter to create, Escape to close
- Auto-focus: Automatically focuses input when opened
- Error Handling: Inline error messages with clear feedback
- Loading States: Disabled buttons during async operations
Usage Example
const [modal, setModal] = useState({ open: false, mode: "file" as const })
const handleNewFile = () => setModal({ open: true, mode: "file" })
const handleCreate = async (name: string) => await createFile(name)
<NewItemModal
open={modal.open}
mode={modal.mode}
onClose={() => setModal({ open: false, mode: "file" })}
onCreate={handleCreate}
existingNames={getExistingNames()}
/>
Styling
CSS Architecture
Location: src/components/Modals/NewItemModal.css
The styling follows these principles:
- Theme Variables: Uses
var(--bg),var(--text),var(--accent)etc. - Responsive Design: Mobile-friendly breakpoints
- Smooth Animations: Backdrop blur and scale effects
- Accessibility: High contrast and focus indicators
Key CSS Classes
.new-item-modal-backdrop: Semi-transparent overlay.new-item-modal: Main modal container.new-item-modal-input: Text input field.new-item-modal-button: Action buttons.new-item-modal-error: Error message display
Integration
State Management
Modals use controlled component patterns:
const [newItemModal, setNewItemModal] = useState<{
open: boolean
mode: NewItemModalMode
}>({
open: false,
mode: "file"
})
Keyboard Shortcuts
Global keyboard event listeners:
- Ctrl+N: Opens new file modal
- Ctrl+Shift+N: Opens new folder modal
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'n' && !e.shiftKey) {
e.preventDefault()
handleNewFile()
}
// ... handle Ctrl+Shift+N
}
document.addEventListener('keydown', handleKeyDown)
return () => document.removeEventListener('keydown', handleKeyDown)
}, [handleNewFile, handleNewFolder])
Validation Rules
Filename Validation
The modal enforces these validation rules:
- Empty Names: Cannot be empty or whitespace only
- Invalid Characters: Prevents
< > : " / \ | ? * - Duplicate Names: Checks against existing workspace items
- Length Limits: Reasonable length constraints
Error Messages
"Name cannot be empty""Name contains invalid characters""File already exists"/"Folder already exists""Failed to create file: [error]"
Best Practices
When to Use Modals
- User Input Required: When you need user input for actions
- Context Preservation: When you don't want to navigate away
- Critical Actions: For important operations like file creation
Controller Integration
Keep business logic in controllers:
// GOOD: Modal handles UI, controller handles logic
const handleModalCreate = async (name: string) => {
if (mode === "file") {
await createFile(name) // Controller method
} else {
await createFolder(name) // Controller method
}
}
// BAD: Modal directly manipulating filesystem
const handleModalCreate = async (name: string) => {
await invoke("create_file", { path: name }) // Don't do this
}
Error Handling
Always handle errors gracefully:
try {
await onCreate(trimmedName)
onClose() // Close on success
} catch (err) {
setError(`Failed to create ${mode}: ${err.message}`)
}
Future Extensions
The modal system is designed for extensibility:
Additional Modal Types
- Confirmation Modals: For destructive actions
- Settings Modals: Complex configuration interfaces
- Import/Export Modals: File handling interfaces
Enhanced Features
- Multi-step Creation: Wizard-style modals
- Template Selection: File template choosers
- Advanced Validation: Custom validation rules
Accessibility
ARIA Attributes
aria-expanded: For folder expansion statearia-invalid: For validation errorsaria-describedby: Links error messages to inputsrole="dialog": Proper semantic role
Keyboard Navigation
- Tab: Navigate between form elements
- Enter: Submit form
- Escape: Close modal
- Focus Management: Auto-focus and restore focus
Testing
Unit Tests
Test modal behavior:
describe('NewItemModal', () => {
it('should validate input correctly')
it('should call onCreate on valid submit')
it('should show error messages for invalid input')
it('should close on Escape key')
})
Integration Tests
Test full workflow:
describe('File Creation Workflow', () => {
it('should create file when modal is submitted')
it('should prevent duplicate file creation')
it('should handle keyboard shortcuts')
})
Troubleshooting
Common Issues
- Modal Not Opening: Check state management and event handlers
- Validation Not Working: Ensure
existingNamesprop is updated - Keyboard Shortcuts Not Working: Check event listener setup
- Styling Issues: Verify CSS variables are available
Debug Tips
- Use React DevTools to inspect modal state
- Check browser console for validation errors
- Verify theme CSS variables are loaded
- Test keyboard shortcuts in different contexts