diff --git a/client/app.js b/client/app.js index b14d8ae..e5dd92b 100644 --- a/client/app.js +++ b/client/app.js @@ -1,94 +1,112 @@ // app.js -(function() { - const status = document.getElementById('status'); - let websocket = null; +import Modal from './design-system/components/modal/modal.js'; - function setStatus(msg) { +const status = document.getElementById('status'); +let websocket = null; +let helpModal = null; + +function setStatus(msg) { + if (status) { status.textContent = msg; } +} + +// Initialize WebSocket connection +function initializeWebSocket() { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const host = window.location.host; + const wsUrl = `${protocol}//${host}/ws`; + + try { + websocket = new WebSocket(wsUrl); + + websocket.onopen = function(event) { + console.log('WebSocket connected'); + setStatus('Ready (WebSocket connected)'); + }; - // Initialize WebSocket connection - function initializeWebSocket() { - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const host = window.location.host; - const wsUrl = `${protocol}//${host}/ws`; - - try { - websocket = new WebSocket(wsUrl); - - websocket.onopen = function(event) { - console.log('WebSocket connected'); - setStatus('Ready (WebSocket connected)'); - }; - - websocket.onmessage = function(event) { - try { - const data = JSON.parse(event.data); - if (data.type === 'message' && data.message) { - alert(data.message); - } - } catch (error) { - console.error('Error parsing WebSocket message:', error); + websocket.onmessage = function(event) { + try { + const data = JSON.parse(event.data); + if (data.type === 'message' && data.message) { + alert(data.message); } - }; - - websocket.onclose = function(event) { - console.log('WebSocket disconnected'); - setStatus('Ready (WebSocket disconnected)'); - - // Attempt to reconnect after 3 seconds - setTimeout(() => { - console.log('Attempting to reconnect WebSocket...'); - initializeWebSocket(); - }, 3000); - }; - - websocket.onerror = function(error) { - console.error('WebSocket error:', error); - setStatus('Ready (WebSocket error)'); - }; - - } catch (error) { - console.error('Failed to create WebSocket connection:', error); - setStatus('Ready (WebSocket unavailable)'); - } + } catch (error) { + console.error('Error parsing WebSocket message:', error); + } + }; + + websocket.onclose = function(event) { + console.log('WebSocket disconnected'); + setStatus('Ready (WebSocket disconnected)'); + + // Attempt to reconnect after 3 seconds + setTimeout(() => { + console.log('Attempting to reconnect WebSocket...'); + initializeWebSocket(); + }, 3000); + }; + + websocket.onerror = function(error) { + console.error('WebSocket error:', error); + setStatus('Ready (WebSocket error)'); + }; + + } catch (error) { + console.error('Failed to create WebSocket connection:', error); + setStatus('Ready (WebSocket unavailable)'); } +} + +// Load help content and initialize modal +async function initializeHelpModal() { + try { + const response = await fetch('./help-content-template.html'); + const helpContent = await response.text(); - // Load help content and initialize modal - async function initializeHelpModal() { - try { - const response = await fetch('./help-content-template.html'); - const helpContent = await response.text(); - - // Initialize help modal with actual content - HelpModal.init({ - triggerSelector: '#btn-help', - content: helpContent, - theme: 'auto' + // Create help modal with actual content + helpModal = Modal.createHelpModal({ + title: 'Help / User Guide', + content: helpContent + }); + + // Bind help button click handler + const helpButton = document.getElementById('btn-help'); + if (helpButton) { + helpButton.addEventListener('click', () => { + helpModal.open(); }); + } - setStatus('Ready'); - } catch (error) { - console.error('Failed to load help content:', error); - // Fallback to placeholder content - HelpModal.init({ - triggerSelector: '#btn-help', - content: '

Help content could not be loaded. Please check that help-content-template.html exists.

', - theme: 'auto' + setStatus('Ready'); + } catch (error) { + console.error('Failed to load help content:', error); + // Fallback to placeholder content + helpModal = Modal.createHelpModal({ + title: 'Help / User Guide', + content: '

Help content could not be loaded. Please check that help-content-template.html exists.

' + }); + + // Bind help button click handler + const helpButton = document.getElementById('btn-help'); + if (helpButton) { + helpButton.addEventListener('click', () => { + helpModal.open(); }); - setStatus('Ready (help content unavailable)'); } - } - // Initialize both help modal and WebSocket when DOM is ready - function initialize() { - initializeHelpModal(); - initializeWebSocket(); + setStatus('Ready (help content unavailable)'); } +} - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initialize); - } else { - initialize(); - } -})(); +// Initialize both help modal and WebSocket when DOM is ready +async function initialize() { + await initializeHelpModal(); + initializeWebSocket(); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initialize); +} else { + initialize(); +} diff --git a/client/bespoke-template.css b/client/bespoke-template.css index 03c3ce7..a8dd69d 100644 --- a/client/bespoke-template.css +++ b/client/bespoke-template.css @@ -90,148 +90,6 @@ /* ===== TEMPORARY COMPONENTS (TODO: Replace when design system adds these) ===== */ -/* Modal Components - TODO: Remove when design system adds modal component */ -.bespoke .modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 500; - display: flex; - align-items: center; - justify-content: center; - padding: var(--UI-Spacing-spacing-xl); - box-sizing: border-box; - margin: 0; -} - -.bespoke .modal-backdrop { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(2px); -} - -.bespoke .modal-content { - position: relative; - background: var(--Colors-Backgrounds-Main-Top); - border: 1px solid var(--Colors-Stroke-Default); - border-radius: var(--UI-Radius-radius-m); - max-width: 800px; - width: calc(100% - 40px); - max-height: 90vh; - display: flex; - flex-direction: column; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); - margin: 0; -} - -.bespoke .modal-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--UI-Spacing-spacing-xl); - border-bottom: 1px solid var(--Colors-Stroke-Default); - background: var(--Colors-Backgrounds-Main-Top); - border-radius: var(--UI-Radius-radius-m) var(--UI-Radius-radius-m) 0 0; -} - -.bespoke .modal-header h2 { - margin: 0; - font-size: var(--Fonts-Body-Default-xl); - color: var(--Colors-Text-Body-Strongest); - font-family: var(--heading-family); - font-weight: 500; -} - -.bespoke .modal-close { - background: none; - border: none; - font-size: var(--Fonts-Body-Default-xxxl); - color: var(--Colors-Text-Body-Medium); - cursor: pointer; - padding: var(--UI-Spacing-spacing-xxs) var(--UI-Spacing-spacing-s); - border-radius: var(--UI-Radius-radius-xxs); - line-height: 1; - transition: all 0.2s ease; -} - -.bespoke .modal-close:hover { - background: var(--Colors-Backgrounds-Main-Medium); - color: var(--Colors-Text-Body-Default); -} - -.bespoke .modal-body { - padding: var(--UI-Spacing-spacing-xl); - overflow-y: auto; - flex: 1; - line-height: 1.6; -} - -.bespoke .modal-body h2 { - margin-top: var(--UI-Spacing-spacing-xxxl); - margin-bottom: var(--UI-Spacing-spacing-ml); - font-size: var(--Fonts-Body-Default-xl); - color: var(--Colors-Text-Body-Strongest); - font-family: var(--heading-family); - font-weight: 500; -} - -.bespoke .modal-body h2:first-child { - margin-top: 0; -} - -.bespoke .modal-body h3 { - margin-top: var(--UI-Spacing-spacing-xl); - margin-bottom: var(--UI-Spacing-spacing-s); - font-size: var(--Fonts-Body-Default-lg); - color: var(--Colors-Text-Body-Strongest); - font-family: var(--heading-family); - font-weight: 500; -} - -.bespoke .modal-body p, -.bespoke .modal-body li { - color: var(--Colors-Text-Body-Default); - margin-bottom: var(--UI-Spacing-spacing-s); -} - -.bespoke .modal-body ul, -.bespoke .modal-body ol { - margin: var(--UI-Spacing-spacing-s) 0 var(--UI-Spacing-spacing-ml) 0; - padding-left: var(--UI-Spacing-spacing-xl); -} - -.bespoke .modal-body code { - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - background: rgba(148, 163, 184, 0.12); - border-radius: var(--UI-Radius-radius-xxs); - padding: 0.15em 0.35em; - font-size: var(--Fonts-Body-Default-xs); -} - -.bespoke .modal-body pre { - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - background: rgba(148, 163, 184, 0.12); - border-radius: var(--UI-Radius-radius-xs); - padding: var(--UI-Spacing-spacing-ms); - overflow: auto; - margin: var(--UI-Spacing-spacing-ml) 0; -} - -.bespoke .modal-body img, -.bespoke .modal-body video { - max-width: 100%; - height: auto; - border-radius: var(--UI-Radius-radius-xs); - border: 1px solid var(--Colors-Stroke-Default); - margin: var(--UI-Spacing-spacing-ml) 0; -} - /* Form Elements - TODO: Remove when design system adds form components */ .bespoke label { display: flex; @@ -247,35 +105,6 @@ margin: var(--UI-Spacing-spacing-s) 0; } -/* Textarea */ -.bespoke textarea { - padding: var(--UI-Spacing-spacing-ms); - border: 1px solid var(--Colors-Input-Border-Default); - border-radius: var(--UI-Radius-radius-s); - background: var(--Colors-Input-Background-Default); - color: var(--Colors-Input-Text-Default); - font-family: var(--body-family); - font-size: var(--Fonts-Body-Default-md); - min-height: 6rem; - resize: vertical; - transition: border-color 0.2s ease; -} - -.bespoke textarea:hover { - border-color: var(--Colors-Input-Border-Hover); -} - -.bespoke textarea:focus-visible { - outline: none; - border-color: var(--Colors-Input-Border-Focus); - box-shadow: 0 0 0 4px var(--Colors-Input-Shadow-Focus); -} - -.bespoke textarea::placeholder { - color: var(--Colors-Input-Text-Placeholder); - opacity: 1; -} - /* Select styling - TODO: Remove when design system adds select component */ .bespoke select { -webkit-appearance: none; @@ -292,66 +121,6 @@ display: none; } -/* Radio Buttons - TODO: Remove when design system adds radio component */ -.bespoke input[type="radio"] { - appearance: none; - width: 1rem; - height: 1rem; - border: 2px solid var(--Colors-Input-Border-Default); - border-radius: 50%; - background: var(--Colors-Input-Background-Default); - cursor: pointer; - position: relative; - transition: all 0.2s ease; - flex-shrink: 0; - padding: 0; -} - -.bespoke input[type="radio"]:checked { - border-color: var(--Colors-Stroke-Primary); - background: var(--Colors-Stroke-Primary); -} - -.bespoke input[type="radio"]:checked::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 0.375rem; - height: 0.375rem; - border-radius: 50%; - background: white; -} - -.bespoke input[type="radio"]:hover { - border-color: var(--Colors-Input-Border-Hover); -} - -.bespoke input[type="radio"]:focus-visible { - outline: none; - border-color: var(--Colors-Input-Border-Focus); - box-shadow: 0 0 0 3px var(--Colors-Input-Shadow-Focus); -} - -.bespoke .radio-group { - display: flex; - flex-direction: column; - gap: var(--UI-Spacing-spacing-s); -} - -.bespoke .radio-group.horizontal { - flex-direction: row; - align-items: center; - gap: var(--UI-Spacing-spacing-ml); -} - -/* Checkbox - TODO: Remove when design system adds checkbox component */ -.bespoke input[type="checkbox"] { - padding: 0; - margin: 0; -} - /* Toggle Switch - TODO: Remove when design system adds toggle component */ .bespoke .toggle { position: relative; @@ -417,15 +186,6 @@ /* Dark mode adjustments */ @media (prefers-color-scheme: dark) { - .bespoke .modal-backdrop { - background: rgba(0, 0, 0, 0.7); - } - - .bespoke .modal-body code, - .bespoke .modal-body pre { - background: rgba(148, 163, 184, 0.2); - } - .bespoke select { background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23c1c7d7' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e"); } @@ -442,25 +202,5 @@ border-right: none; border-bottom: 1px solid var(--Colors-Stroke-Default); } - - .bespoke .modal { - padding: var(--UI-Spacing-spacing-s); - } - - .bespoke .modal-content { - max-height: 95vh; - } - - .bespoke .modal-header { - padding: var(--UI-Spacing-spacing-mxl); - } - - .bespoke .modal-body { - padding: var(--UI-Spacing-spacing-mxl); - } - - .bespoke .modal-header h2 { - font-size: var(--Fonts-Body-Default-lg); - } } diff --git a/client/design-system b/client/design-system index 782c016..a298587 160000 --- a/client/design-system +++ b/client/design-system @@ -1 +1 @@ -Subproject commit 782c016346416157a81f1ba8e4782bfacb9e20be +Subproject commit a2985876e55f8a6bd0e94ba5f72c74d2a372e2a9 diff --git a/client/example-app/example-app.js b/client/example-app/example-app.js index 3ccaf24..ca1cb12 100644 --- a/client/example-app/example-app.js +++ b/client/example-app/example-app.js @@ -1,14 +1,16 @@ // example-app.js // Interactive Component Showcase Application -(function() { - const status = document.getElementById('status'); +import Modal from '../design-system/components/modal/modal.js'; - // App state - let counterValue = 0; - let incrementAmount = 1; - let counterLabel = 'Counter'; - let dropdownInstance = null; +const status = document.getElementById('status'); + +// App state +let counterValue = 0; +let incrementAmount = 1; +let counterLabel = 'Counter'; +let dropdownInstance = null; +let helpModal = null; function setStatus(msg) { if (status) { @@ -154,36 +156,46 @@ const response = await fetch('./help-content.html'); const helpContent = await response.text(); - if (typeof HelpModal !== 'undefined') { - HelpModal.init({ - triggerSelector: '#btn-help', - content: helpContent, - theme: 'auto' + // Create help modal with actual content + helpModal = Modal.createHelpModal({ + title: 'Help / User Guide', + content: helpContent + }); + + // Bind help button click handler + const helpButton = document.getElementById('btn-help'); + if (helpButton) { + helpButton.addEventListener('click', () => { + helpModal.open(); }); - } else { - console.error('HelpModal not found. Make sure help-modal.js is loaded.'); } } catch (error) { console.error('Failed to load help content:', error); - if (typeof HelpModal !== 'undefined') { - HelpModal.init({ - triggerSelector: '#btn-help', - content: '

Help content could not be loaded. Please check that help-content.html exists.

', - theme: 'auto' + // Fallback to placeholder content + helpModal = Modal.createHelpModal({ + title: 'Help / User Guide', + content: '

Help content could not be loaded. Please check that help-content.html exists.

' + }); + + // Bind help button click handler + const helpButton = document.getElementById('btn-help'); + if (helpButton) { + helpButton.addEventListener('click', () => { + helpModal.open(); }); } } } // Initialize everything when DOM is ready - function initialize() { + async function initialize() { setStatus('Loading...'); // Initialize event listeners initializeEventListeners(); // Initialize help modal - initializeHelpModal(); + await initializeHelpModal(); // Initialize dropdown after a short delay to ensure Dropdown class is loaded setTimeout(() => { @@ -198,4 +210,3 @@ } else { initialize(); } -})(); diff --git a/client/example-app/index.html b/client/example-app/index.html index dbd07c1..cef11e6 100644 --- a/client/example-app/index.html +++ b/client/example-app/index.html @@ -20,6 +20,7 @@ + @@ -92,13 +93,8 @@

Current Settings

- - - - - - + diff --git a/client/help-content-template.html b/client/help-content.html similarity index 100% rename from client/help-content-template.html rename to client/help-content.html diff --git a/client/help-modal.js b/client/help-modal.js deleted file mode 100644 index 34d45d8..0000000 --- a/client/help-modal.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * HelpModal - A reusable, dependency-free help modal system for Bespoke applications - * - * This modal system is designed to work with the CodeSignal Design System and provides - * a consistent help experience across all embedded applications. - * - * Note: Currently uses temporary modal components from bespoke-template.css. - * When modal components are added to the design system, this should be updated to use them. - * - * Usage: - * HelpModal.init({ - * triggerSelector: '#btn-help', - * content: helpContent, - * theme: 'auto' - * }); - */ - -class HelpModal { - constructor(options = {}) { - this.options = { - triggerSelector: '#btn-help', - content: '', - theme: 'auto', // 'light', 'dark', or 'auto' - customStyles: {}, - ...options - }; - - this.isOpen = false; - this.modal = null; - this.trigger = null; - - this.init(); - } - - init() { - this.createModal(); - this.bindEvents(); - } - - createModal() { - // Create modal container using temporary modal classes from bespoke-template.css - // TODO: Update to use design system modal classes when available - this.modal = document.createElement('div'); - this.modal.className = 'modal'; - this.modal.innerHTML = ` - - - `; - - // Initially hidden - this.modal.style.display = 'none'; - document.body.appendChild(this.modal); - } - - bindEvents() { - // Find trigger element - this.trigger = document.querySelector(this.options.triggerSelector); - if (!this.trigger) { - console.warn(`HelpModal: Trigger element '${this.options.triggerSelector}' not found`); - return; - } - - // Convert link to button if needed - if (this.trigger.tagName === 'A') { - this.trigger.addEventListener('click', (e) => { - e.preventDefault(); - this.open(); - }); - } else { - this.trigger.addEventListener('click', () => this.open()); - } - - // Close button - const closeBtn = this.modal.querySelector('.modal-close'); - closeBtn.addEventListener('click', () => this.close()); - - // Backdrop click - const backdrop = this.modal.querySelector('.modal-backdrop'); - backdrop.addEventListener('click', () => this.close()); - - // ESC key - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && this.isOpen) { - this.close(); - } - }); - - // Handle internal navigation links - this.modal.addEventListener('click', (e) => { - if (e.target.matches('a[href^="#"]')) { - e.preventDefault(); - const targetId = e.target.getAttribute('href').substring(1); - const targetElement = this.modal.querySelector(`#${targetId}`); - if (targetElement) { - targetElement.scrollIntoView({ behavior: 'smooth' }); - } - } - }); - } - - open() { - if (this.isOpen) return; - - this.isOpen = true; - this.modal.style.display = 'flex'; // Use flex to center the modal - document.body.style.overflow = 'hidden'; // Prevent background scrolling - - // Focus management - const closeBtn = this.modal.querySelector('.modal-close'); - closeBtn.focus(); - - // Trigger custom event - this.trigger.dispatchEvent(new CustomEvent('helpModal:open', { detail: this })); - } - - close() { - if (!this.isOpen) return; - - this.isOpen = false; - this.modal.style.display = 'none'; - document.body.style.overflow = ''; // Restore scrolling - - // Return focus to trigger - this.trigger.focus(); - - // Trigger custom event - this.trigger.dispatchEvent(new CustomEvent('helpModal:close', { detail: this })); - } - - // Public API methods - static init(options) { - return new HelpModal(options); - } - - destroy() { - if (this.modal && this.modal.parentNode) { - this.modal.parentNode.removeChild(this.modal); - } - document.body.style.overflow = ''; - } - - // Method to update content dynamically - updateContent(newContent) { - const modalBody = this.modal.querySelector('.modal-body'); - if (modalBody) { - modalBody.innerHTML = newContent; - } - } -} - -// Export for use -if (typeof module !== 'undefined' && module.exports) { - module.exports = HelpModal; -} else { - window.HelpModal = HelpModal; -} diff --git a/client/index.html b/client/index.html index 7fb61b8..1922d93 100644 --- a/client/index.html +++ b/client/index.html @@ -20,6 +20,7 @@ + @@ -38,16 +39,8 @@

APP NAME

- - - - - + - -