Skip to content

Commit e9dbe56

Browse files
Merge pull request #1 from CodeSignal/help-button-improvement
Added help modal
2 parents 186ac48 + 61f4cf1 commit e9dbe56

File tree

7 files changed

+609
-45
lines changed

7 files changed

+609
-45
lines changed

HELP-MODAL-README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Help Modal System
2+
3+
A reusable, dependency-free help modal system that can be easily extracted and used in other projects.
4+
5+
## Files
6+
7+
- `help-modal.js` - The main modal system (standalone, no dependencies)
8+
- `help-content.html` - The help content (can be customized)
9+
- `style.css` - Contains modal styles (integrated with existing theme system)
10+
11+
## Usage
12+
13+
### Basic Integration
14+
15+
1. Include the modal script in your HTML:
16+
```html
17+
<script src="./help-modal.js"></script>
18+
```
19+
20+
2. Add a trigger button:
21+
```html
22+
<button id="btn-help" class="as-button ghost">HELP</button>
23+
```
24+
25+
3. Initialize the modal:
26+
```javascript
27+
HelpModal.init({
28+
triggerSelector: '#btn-help',
29+
content: helpContent,
30+
theme: 'auto'
31+
});
32+
```
33+
34+
### Configuration Options
35+
36+
```javascript
37+
HelpModal.init({
38+
triggerSelector: '#btn-help', // CSS selector for trigger element
39+
content: helpContent, // HTML content string or fetch from URL
40+
theme: 'auto', // 'light', 'dark', or 'auto'
41+
customStyles: {} // Optional style overrides
42+
});
43+
```
44+
45+
### Loading Content from File
46+
47+
```javascript
48+
const helpContent = await fetch('./help-content.html').then(r => r.text());
49+
HelpModal.init({
50+
triggerSelector: '#btn-help',
51+
content: helpContent,
52+
theme: 'auto'
53+
});
54+
```
55+
56+
## Features
57+
58+
-**Dependency-free**: Pure vanilla JavaScript
59+
-**Theme-aware**: Automatically adapts to light/dark themes
60+
-**Responsive**: Works on mobile and desktop
61+
-**Accessible**: Keyboard navigation (ESC to close)
62+
-**Reusable**: Easy to extract and use in other projects
63+
-**Image support**: Handles images and rich content
64+
-**Navigation**: Internal anchor links work within modal
65+
66+
## Extraction for Other Projects
67+
68+
To use this modal system in another project:
69+
70+
1. Copy `help-modal.js` to your project
71+
2. Copy the modal styles from `style.css` (lines 202-410)
72+
3. Adapt the CSS custom properties to match your theme
73+
4. Create your help content
74+
5. Initialize with `HelpModal.init()`
75+
76+
## Customization
77+
78+
### Styling
79+
The modal uses CSS custom properties that integrate with your existing theme:
80+
- `--bg`, `--fg`, `--box`, `--stroke`, `--muted` for colors
81+
- `--control-bg`, `--control-border` for interactive elements
82+
83+
### Content
84+
The help content supports:
85+
- Standard HTML elements
86+
- Images with relative paths
87+
- Internal navigation links
88+
- Collapsible `<details>` sections
89+
- Code blocks and inline code
90+
91+
## Browser Support
92+
93+
Works in all modern browsers that support:
94+
- ES6 classes
95+
- CSS custom properties
96+
- Flexbox
97+
- `fetch()` API (for loading external content)

app.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,20 @@
446446
// click empty canvas to clear selection (and exit column edit mode)
447447
svg.addEventListener('click', () => { selectTable(null); clearFieldForm(); });
448448

449+
// --- Initialize Help Modal
450+
(async function initHelpModal() {
451+
try {
452+
const helpContent = await fetch('./help-content.html').then(r => r.text());
453+
HelpModal.init({
454+
triggerSelector: '#btn-help',
455+
content: helpContent,
456+
theme: 'auto'
457+
});
458+
} catch (e) {
459+
console.warn('Failed to load help content:', e);
460+
}
461+
})();
462+
449463
// --- boot
450464
(async function init() {
451465
try {

help-content.html

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<!-- Help content for the modal - extracted from help/index.html -->
2+
<nav class="toc">
3+
<strong>Contents</strong>
4+
<ul>
5+
<li><a href="#overview">Overview</a></li>
6+
<li><a href="#pan-zoom">Pan &amp; Zoom</a></li>
7+
<li><a href="#moving-tables">Moving Tables</a></li>
8+
<li><a href="#fields">Adding &amp; Editing Fields</a></li>
9+
<li><a href="#keys">Primary &amp; Unique Keys</a></li>
10+
<li><a href="#foreign-keys">Foreign Keys</a></li>
11+
<li><a href="#saving">Saving &amp; Loading</a></li>
12+
<li><a href="#shortcuts">Shortcuts</a></li>
13+
<li><a href="#faq">Troubleshooting / FAQ</a></li>
14+
</ul>
15+
</nav>
16+
17+
<section id="overview">
18+
<h2>Overview</h2>
19+
<p>This page explains how to use the DB Schema Designer: create tables, add fields, set keys, connect tables, and save your work.</p>
20+
<!-- Example hero image: drop files into help/img/ and link relatively -->
21+
<!-- <img src="./img/overview.png" alt="Overview of the UI"> -->
22+
</section>
23+
24+
<section id="pan-zoom">
25+
<h2>Pan &amp; Zoom</h2>
26+
<ul>
27+
<li>Use the mouse wheel to <em>zoom</em> in/out. The view zooms around your cursor.</li>
28+
<li><strong>Drag the empty background</strong> to pan the canvas.</li>
29+
</ul>
30+
<!-- <img src="./img/pan-zoom.gif" alt="Pan and zoom demonstration"> -->
31+
</section>
32+
33+
<section id="moving-tables">
34+
<h2>Moving Tables</h2>
35+
<p>Grab the dotted handle in the table header to drag a table.</p>
36+
<!-- <img src="./img/drag-handle.png" alt="The drag handle on a table"> -->
37+
</section>
38+
39+
<section id="fields">
40+
<h2>Adding &amp; Editing Fields</h2>
41+
<ol>
42+
<li>Select a table from the diagram.</li>
43+
<li>Use the <em>Add Field</em> form in the sidebar: name, type, nullable/default, primary/unique.</li>
44+
<li>Click a field in the diagram to edit it again.</li>
45+
</ol>
46+
<!-- <img src="./img/add-field.png" alt="Adding a field"> -->
47+
</section>
48+
49+
<section id="keys">
50+
<h2>Primary &amp; Unique Keys</h2>
51+
<p>Mark a field as <strong>Primary key</strong> or <strong>Unique</strong> in the field form. Primary keys are shown with a key icon.</p>
52+
</section>
53+
54+
<section id="foreign-keys">
55+
<h2>Foreign Keys</h2>
56+
<ol>
57+
<li>Choose the <em>From column</em>, <em>To table</em>, and <em>To column</em>.</li>
58+
<li>Pick an <em>On Delete</em> action (e.g., CASCADE).</li>
59+
<li>Click <em>Add FK</em>. Arrows will appear between related tables.</li>
60+
</ol>
61+
<p>To remove an FK for a field, select the field and use the <em>Remove</em> option (status text will confirm).</p>
62+
<!-- <img src="./img/add-fk.png" alt="Adding a foreign key"> -->
63+
</section>
64+
65+
<section id="saving">
66+
<h2>Saving &amp; Loading</h2>
67+
<ul>
68+
<li><strong>Save solution</strong> – saves your schema to the server endpoint the app is configured with.</li>
69+
<li>Local autosave may also use <code>localStorage</code> when enabled.</li>
70+
</ul>
71+
</section>
72+
73+
<section id="shortcuts">
74+
<h2>Shortcuts</h2>
75+
<ul>
76+
<li><strong>ESC</strong> - Close help modal</li>
77+
<li><strong>Mouse wheel</strong> - Zoom in/out</li>
78+
<li><strong>Drag background</strong> - Pan canvas</li>
79+
<li><strong>Click table</strong> - Select table</li>
80+
<li><strong>Click field</strong> - Edit field</li>
81+
</ul>
82+
</section>
83+
84+
<section id="faq">
85+
<h2>Troubleshooting / FAQ</h2>
86+
<details>
87+
<summary>How do I delete a table?</summary>
88+
<p>Select the table and click the "Delete" button in the sidebar. This will also remove any foreign keys connected to the table.</p>
89+
</details>
90+
91+
<details>
92+
<summary>Can I change the color of tables?</summary>
93+
<p>Yes! Select a table and use the "Table color" dropdown in the sidebar to choose from white, blue, green, or red.</p>
94+
</details>
95+
96+
<details>
97+
<summary>How do I add images to the help?</summary>
98+
<p>Place image files in the <code>help/img/</code> directory and reference them with relative paths like <code>&lt;img src="./img/example.png" alt="Description"&gt;</code></p>
99+
</details>
100+
101+
<details>
102+
<summary>Is my work automatically saved?</summary>
103+
<p>Yes, the app automatically saves your schema every second to both localStorage and the server. You'll see "Auto-saved" status messages.</p>
104+
</details>
105+
</section>

help-modal.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* HelpModal - A reusable, dependency-free help modal system
3+
*
4+
* Usage:
5+
* HelpModal.init({
6+
* triggerSelector: '#btn-help',
7+
* content: helpContent,
8+
* theme: 'auto'
9+
* });
10+
*/
11+
12+
class HelpModal {
13+
constructor(options = {}) {
14+
this.options = {
15+
triggerSelector: '#btn-help',
16+
content: '',
17+
theme: 'auto', // 'light', 'dark', or 'auto'
18+
customStyles: {},
19+
...options
20+
};
21+
22+
this.isOpen = false;
23+
this.modal = null;
24+
this.trigger = null;
25+
26+
this.init();
27+
}
28+
29+
init() {
30+
this.createModal();
31+
this.bindEvents();
32+
}
33+
34+
createModal() {
35+
// Create modal container
36+
this.modal = document.createElement('div');
37+
this.modal.className = 'help-modal';
38+
this.modal.innerHTML = `
39+
<div class="help-modal-backdrop"></div>
40+
<div class="help-modal-content">
41+
<div class="help-modal-header">
42+
<h2>Help / User Guide</h2>
43+
<button class="help-modal-close" type="button" aria-label="Close help">×</button>
44+
</div>
45+
<div class="help-modal-body">
46+
${this.options.content}
47+
</div>
48+
</div>
49+
`;
50+
51+
// Initially hidden
52+
this.modal.style.display = 'none';
53+
document.body.appendChild(this.modal);
54+
}
55+
56+
bindEvents() {
57+
// Find trigger element
58+
this.trigger = document.querySelector(this.options.triggerSelector);
59+
if (!this.trigger) {
60+
console.warn(`HelpModal: Trigger element '${this.options.triggerSelector}' not found`);
61+
return;
62+
}
63+
64+
// Convert link to button if needed
65+
if (this.trigger.tagName === 'A') {
66+
this.trigger.addEventListener('click', (e) => {
67+
e.preventDefault();
68+
this.open();
69+
});
70+
} else {
71+
this.trigger.addEventListener('click', () => this.open());
72+
}
73+
74+
// Close button
75+
const closeBtn = this.modal.querySelector('.help-modal-close');
76+
closeBtn.addEventListener('click', () => this.close());
77+
78+
// Backdrop click
79+
const backdrop = this.modal.querySelector('.help-modal-backdrop');
80+
backdrop.addEventListener('click', () => this.close());
81+
82+
// ESC key
83+
document.addEventListener('keydown', (e) => {
84+
if (e.key === 'Escape' && this.isOpen) {
85+
this.close();
86+
}
87+
});
88+
89+
// Handle internal navigation links
90+
this.modal.addEventListener('click', (e) => {
91+
if (e.target.matches('a[href^="#"]')) {
92+
e.preventDefault();
93+
const targetId = e.target.getAttribute('href').substring(1);
94+
const targetElement = this.modal.querySelector(`#${targetId}`);
95+
if (targetElement) {
96+
targetElement.scrollIntoView({ behavior: 'smooth' });
97+
}
98+
}
99+
});
100+
}
101+
102+
open() {
103+
if (this.isOpen) return;
104+
105+
this.isOpen = true;
106+
this.modal.style.display = 'block';
107+
document.body.style.overflow = 'hidden'; // Prevent background scrolling
108+
109+
// Focus management
110+
const closeBtn = this.modal.querySelector('.help-modal-close');
111+
closeBtn.focus();
112+
113+
// Trigger custom event
114+
this.trigger.dispatchEvent(new CustomEvent('helpModal:open', { detail: this }));
115+
}
116+
117+
close() {
118+
if (!this.isOpen) return;
119+
120+
this.isOpen = false;
121+
this.modal.style.display = 'none';
122+
document.body.style.overflow = ''; // Restore scrolling
123+
124+
// Return focus to trigger
125+
this.trigger.focus();
126+
127+
// Trigger custom event
128+
this.trigger.dispatchEvent(new CustomEvent('helpModal:close', { detail: this }));
129+
}
130+
131+
// Public API methods
132+
static init(options) {
133+
return new HelpModal(options);
134+
}
135+
136+
destroy() {
137+
if (this.modal && this.modal.parentNode) {
138+
this.modal.parentNode.removeChild(this.modal);
139+
}
140+
document.body.style.overflow = '';
141+
}
142+
}
143+
144+
// Export for use
145+
window.HelpModal = HelpModal;

index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<h1>DB Schema Designer</h1>
1212
<button id="btn-save-solution">Save solution</button>
1313
<div id="status">Ready</div>
14-
<a id="btn-help" class="as-button ghost" href="./help/" target="_blank" rel="noopener">HELP</a>
14+
<button id="btn-help" class="as-button ghost">HELP</button>
1515
</header>
1616

1717
<main class="layout">
@@ -107,6 +107,7 @@ <h3>Add Foreign Key</h3>
107107
<script src="./io.js"></script>
108108
<script src="./diagram.js"></script>
109109
<script src="./ui.js"></script>
110+
<script src="./help-modal.js"></script>
110111
<script src="./app.js"></script>
111112
</body>
112113
</html>

0 commit comments

Comments
 (0)