diff --git a/css/common.css b/css/common.css index eb2c832..99a4a8a 100644 --- a/css/common.css +++ b/css/common.css @@ -11,6 +11,7 @@ Version: 0.1: added line-height 1.5 for readability and spacing between labels a --p-sm: 0.5rem; --p-md: 1rem; --sm: 600px; + --max-content-width: 75rem; } body { @@ -26,6 +27,19 @@ body { color 0.5s; } +main { + max-width: var(--max-content-width); + margin: 0 auto; + width: 100%; + padding: var(--p-md); +} + +main.full-width { + max-width: none; + margin: 0; + padding: 0 !important; +} + footer { bottom: 0; width: 100%; diff --git a/index.html b/index.html index a39a460..d9d41dc 100644 --- a/index.html +++ b/index.html @@ -30,7 +30,7 @@ --> -
+
diff --git a/js/components/code-viewer.js b/js/components/code-viewer.js new file mode 100644 index 0000000..7a7f676 --- /dev/null +++ b/js/components/code-viewer.js @@ -0,0 +1,58 @@ +// File: js/components/code-viewer.js +// Component for displaying code snippets in multiple languages with a selector + +export default (hostComponent) => { + const pres = Array.from(hostComponent.querySelectorAll('pre[data-lang]')); + if (pres.length === 0) return; + + const selector = document.createElement('select'); + pres.forEach((pre, index) => { + const { lang } = pre.dataset; + const code = pre.querySelector('code'); + if (code) { + code.classList.add(`language-${lang}`); + } + const option = document.createElement('option'); + option.value = lang; + option.textContent = lang; + selector.appendChild(option); + pre.style.display = index === 0 ? '' : 'none'; + }); + + selector.addEventListener('change', (event) => { + const lang = event.target.value; + pres.forEach((pre) => { + pre.style.display = pre.dataset.lang === lang ? '' : 'none'; + }); + }); + + hostComponent.insertBefore(selector, hostComponent.firstChild); + + const ensureHighlight = () => + new Promise((resolve) => { + if (window.hljs) { + resolve(); + return; + } + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = + 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css'; + document.head.appendChild(link); + const script = document.createElement('script'); + script.src = + 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js'; + script.onload = resolve; + document.head.appendChild(script); + }); + + ensureHighlight().then(() => { + pres.forEach((pre) => { + const code = pre.querySelector('code'); + if (code && window.hljs?.highlightElement) { + window.hljs.highlightElement(code); + } + }); + }); +}; + diff --git a/js/components/code-viewer.test.js b/js/components/code-viewer.test.js new file mode 100644 index 0000000..123ea4b --- /dev/null +++ b/js/components/code-viewer.test.js @@ -0,0 +1,42 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import codeViewer from './code-viewer.js'; + +beforeEach(() => { + // Mock hljs to avoid loading external scripts during tests + vi.stubGlobal('hljs', { + highlightElement: vi.fn(), + }); +}); + +describe('code-viewer component', () => { + it('renders selector and shows only first snippet', () => { + const host = document.createElement('div'); + host.innerHTML = ` +
console.log('hi')
+
print('hi')
+ `; + codeViewer(host); + const select = host.querySelector('select'); + expect(select).toBeTruthy(); + const pres = host.querySelectorAll('pre[data-lang]'); + expect(pres[0].style.display).toBe(''); + expect(pres[1].style.display).toBe('none'); + }); + + it('switches visible snippet on selector change', () => { + const host = document.createElement('div'); + host.innerHTML = ` +
console.log('hi')
+
print('hi')
+ `; + codeViewer(host); + const select = host.querySelector('select'); + select.value = 'python'; + select.dispatchEvent(new Event('change')); + const jsPre = host.querySelector('pre[data-lang="js"]'); + const pyPre = host.querySelector('pre[data-lang="python"]'); + expect(jsPre.style.display).toBe('none'); + expect(pyPre.style.display).toBe(''); + }); +}); + diff --git a/js/components/nav.js b/js/components/nav.js index 3d537c0..278ca37 100644 --- a/js/components/nav.js +++ b/js/components/nav.js @@ -44,6 +44,9 @@ export default (hostComponent) => { flex-direction: column; gap: 0.4rem; padding: 10px 10px; + max-width: var(--max-content-width); + margin: 0 auto; + width: 100%; background-color: var(--nav-background-color); z-index: 10; a { @@ -79,6 +82,12 @@ export default (hostComponent) => { } } } + nav.overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + } .burger-button { position: absolute; right: 0; @@ -191,6 +200,10 @@ export default (hostComponent) => { 🧮 Web GPU tutorial + + 💻 + Developers +