mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
203 lines
5.3 KiB
Markdown
203 lines
5.3 KiB
Markdown
# Modal System Design
|
|
|
|
**Date:** 2026-03-24
|
|
**Version:** 4.0.0
|
|
**Status:** Approved
|
|
|
|
## Overview
|
|
|
|
Replace the existing dialog implementations with a unified modal system that provides:
|
|
- Glassmorphism backdrop matching app aesthetic
|
|
- Full accessibility (ARIA, focus trap, keyboard navigation)
|
|
- Smooth fade + scale animations
|
|
- Consistent API via `ModalManager` class
|
|
|
|
## Decisions Made
|
|
|
|
| Decision | Choice | Rationale |
|
|
|----------|--------|-----------|
|
|
| Architecture | Unified `ModalManager` class | Cleaner, consistent behavior across all modals |
|
|
| Backdrop style | Glassmorphism | Matches existing app design language |
|
|
| Focus management | Focus first interactive element | Standard, predictable behavior |
|
|
| Animation | Fade + scale (95% → 100%) | Modern, subtle effect |
|
|
| Implementation | Custom (not native `<dialog>`) | Full control, no polyfill concerns |
|
|
|
|
## Architecture
|
|
|
|
### File Structure
|
|
|
|
```
|
|
src/
|
|
├── utils/
|
|
│ └── ModalManager.js # Core modal logic (~150 lines)
|
|
├── styles/
|
|
│ └── modal.css # Unified modal styles (~200 lines)
|
|
└── index.html # Updated dialog markup
|
|
```
|
|
|
|
### ModalManager Class
|
|
|
|
```javascript
|
|
class ModalManager {
|
|
constructor(element, options = {})
|
|
open() // Show modal with animation
|
|
close() // Hide modal with animation
|
|
destroy() // Cleanup event listeners
|
|
on(event, callback) // Event subscription
|
|
|
|
// Internal
|
|
#createBackdrop() // Create glassmorphism backdrop
|
|
#trapFocus() // Manage focus within modal
|
|
#handleKeydown(e) // Escape key handler
|
|
#getFocusableElements() // Query focusable children
|
|
}
|
|
```
|
|
|
|
### Events
|
|
|
|
- `open` — Fired after open animation completes
|
|
- `close` — Fired after close animation completes
|
|
|
|
## CSS Design
|
|
|
|
### Variables (from tokens.css)
|
|
|
|
```css
|
|
--z-modal: 200;
|
|
--transition-normal: 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
|
|
--radius-lg: 0.5rem;
|
|
```
|
|
|
|
### Backdrop
|
|
|
|
```css
|
|
.modal-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
z-index: var(--z-modal);
|
|
}
|
|
```
|
|
|
|
### Modal Container
|
|
|
|
```css
|
|
.modal {
|
|
position: fixed;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: calc(var(--z-modal) + 1);
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: opacity var(--transition-normal),
|
|
visibility var(--transition-normal);
|
|
}
|
|
|
|
.modal.open {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
```
|
|
|
|
### Modal Content (with animation)
|
|
|
|
```css
|
|
.modal-content {
|
|
background: hsl(var(--background));
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-xl);
|
|
max-width: 90vw;
|
|
max-height: 90vh;
|
|
overflow: hidden;
|
|
transform: scale(0.95);
|
|
transition: transform var(--transition-normal);
|
|
}
|
|
|
|
.modal.open .modal-content {
|
|
transform: scale(1);
|
|
}
|
|
```
|
|
|
|
## HTML Structure
|
|
|
|
All dialogs convert to unified structure:
|
|
|
|
```html
|
|
<div id="export-dialog"
|
|
class="modal"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="export-dialog-title">
|
|
|
|
<div class="modal-backdrop" data-close></div>
|
|
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 id="export-dialog-title">Export Options</h3>
|
|
<button class="modal-close" aria-label="Close">×</button>
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
<!-- Dialog-specific content -->
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" data-close>Cancel</button>
|
|
<button class="btn btn-primary">Confirm</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Key Attributes
|
|
|
|
- `role="dialog"` — Screen reader identification
|
|
- `aria-modal="true"` — Prevents screen reader from accessing background
|
|
- `aria-labelledby` — References the dialog title
|
|
- `data-close` — Click handler for closing (backdrop, cancel buttons)
|
|
|
|
## Accessibility Features
|
|
|
|
1. **Focus trap** — Tab cycles within modal only
|
|
2. **Focus first element** — Auto-focuses first input/button on open
|
|
3. **Escape key** — Closes modal
|
|
4. **Click outside** — Clicking backdrop closes modal
|
|
5. **Focus restoration** — Returns focus to trigger element on close
|
|
6. **ARIA attributes** — Proper screen reader support
|
|
|
|
## Dialogs to Migrate
|
|
|
|
| Dialog ID | Current Class | Complexity |
|
|
|-----------|--------------|------------|
|
|
| `find-dialog` | `.find-dialog` | Simple |
|
|
| `export-dialog` | `.export-dialog` | Complex (many sections) |
|
|
| `print-preview-overlay` | `.export-dialog` | Medium |
|
|
| `table-generator-dialog` | `.export-dialog` | Simple |
|
|
| `ascii-art-dialog` | `.export-dialog` | Medium |
|
|
| `universal-converter-dialog` | `.export-dialog` | Complex |
|
|
| `batch-dialog` | `.batch-dialog` | Complex |
|
|
| `pdf-editor-dialog` | `.export-dialog` | Complex |
|
|
| `header-footer-dialog` | `.export-dialog` | Medium |
|
|
| `field-picker-dialog` | `.export-dialog` | Simple |
|
|
|
|
## Migration Steps
|
|
|
|
1. Create `src/utils/ModalManager.js`
|
|
2. Create `src/styles/modal.css`
|
|
3. Update `index.html` to include new stylesheet
|
|
4. Convert each dialog HTML to new structure
|
|
5. Initialize `ModalManager` instances in `renderer.js`
|
|
6. Remove old CSS from `styles.css`
|
|
7. Test all dialogs
|
|
|
|
## Out of Scope
|
|
|
|
- Modal nesting (stacked modals) — can be added later if needed
|
|
- Animated backdrop (currently static blur)
|
|
- Modal size variants (small/large/fullscreen) — can use inline styles
|