diff --git a/cspell.json b/cspell.json index 09559f4fb..fd6d496c1 100644 --- a/cspell.json +++ b/cspell.json @@ -67,6 +67,7 @@ "formtarget", "FOUC", "FOUCE", + "Frontmatter", "fullscreen", "gestern", "giga", diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 02f3a1e6f..e8feaadce 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -124,7 +124,7 @@ export default function (eleventyConfig) { eleventyConfig.addPlugin(currentLink()); // Add code examples for `` blocks - eleventyConfig.addPlugin(codeExamplesPlugin); + eleventyConfig.addPlugin(codeExamplesPlugin()); // Highlight code blocks with Prism eleventyConfig.addPlugin(highlightCodePlugin()); diff --git a/docs/_includes/base.njk b/docs/_includes/base.njk index bf0e7e466..473f7897a 100644 --- a/docs/_includes/base.njk +++ b/docs/_includes/base.njk @@ -4,7 +4,7 @@ {% include 'head.njk' %} - + diff --git a/docs/_layouts/patterns.njk b/docs/_layouts/patterns.njk index 4bd2bd527..cb2836dd6 100644 --- a/docs/_layouts/patterns.njk +++ b/docs/_layouts/patterns.njk @@ -1,5 +1 @@ {% extends '../_layouts/block.njk' %} - -{% block head %} - -{% endblock %} \ No newline at end of file diff --git a/docs/_utils/code-examples.js b/docs/_utils/code-examples.js index ed2da68bb..f0492db1a 100644 --- a/docs/_utils/code-examples.js +++ b/docs/_utils/code-examples.js @@ -1,185 +1,85 @@ import { parse } from 'node-html-parser'; import { v4 as uuid } from 'uuid'; -const templates = { - old(pre, code, { open, buttons, edit }) { - const id = `code-example-${uuid().slice(-12)}`; - let preview = code.textContent; - - // Run preview scripts as modules to prevent collisions - const root = parse(preview, { blockTextElements: { script: true } }); - root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module')); - preview = root.toString(); - - return ` -
-
- ${preview} -
-
- ${pre.outerHTML} -
- ${ - buttons - ? ` -
- - ${ - edit - ? ` - - ` - : '' - } - ` - : '' - } -
-
- `; - }, - new(pre, code, { open, first, attributes }) { - attributes = { - open, - include: `link[rel=stylesheet][href^='/dist/']`, - ...attributes, - }; - - const attributesString = Object.entries(attributes) - .map(([key, value]) => { - if (value === true) { - return key; - } - if (value === false || value === null) { - return ''; - } - return `${key}="${value}"`; - }) - .join(' '); - - let includes = ''; - if (first) { - includes = ` - `; - } - - let preview = ''; - if (attributes.viewport === undefined) { - // Slot in pre-rendered preview - preview = `
${code.textContent}
`; - - // Run preview scripts as modules to prevent collisions - const root = parse(preview, { blockTextElements: { script: true } }); - root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module')); - preview = root.toString(); - } - - return `${includes} - - ${preview} - ${pre.outerHTML} - - `; - }, -}; - /** * Eleventy plugin to turn `` blocks into live examples. */ -export function codeExamplesPlugin(eleventyConfig, options = {}) { - const defaultOptions = { +export function codeExamplesPlugin(options = {}) { + options = { container: 'body', - defaultOpen: () => false, - }; - options = { ...defaultOptions, ...options }; - - const stats = { - inputPaths: {}, - outputPaths: {}, + ...options, }; - eleventyConfig.addTransform('code-examples', function (content) { - const { inputPath, outputPath } = this.page; + return function (eleventyConfig) { + eleventyConfig.addTransform('code-examples', content => { + const doc = parse(content, { blockTextElements: { code: true } }); + const container = doc.querySelector(options.container); - const doc = parse(content, { blockTextElements: { code: true } }); - const container = doc.querySelector(options.container); - - if (!container) { - return content; - } - - // Look for external links - container.querySelectorAll('code.example').forEach(code => { - stats.inputPaths[inputPath] ??= 0; - stats.outputPaths[outputPath] ??= 0; - stats.inputPaths[inputPath]++; - stats.outputPaths[outputPath]++; - - const pre = code.closest('pre'); - const first = stats.inputPaths[inputPath] === 1; - - const localOptions = { - ...options, - first, - - // Modifier defaults - edit: true, - buttons: true, - new: true, // comment this line to default back to the old demos - attributes: {}, - }; - - for (const prop of ['new', 'open', 'buttons', 'edit']) { - if (code.classList.contains(prop)) { - localOptions[prop] = true; - } else if (code.classList.contains(`no-${prop}`)) { - localOptions[prop] = false; - } + if (!container) { + return content; } - for (const attribute of ['viewport', 'include']) { - if (code.hasAttribute(attribute)) { - localOptions.attributes[attribute] = code.getAttribute(attribute); - code.removeAttribute(attribute); - } - } + // Look for external links + container.querySelectorAll('code.example').forEach(code => { + const pre = code.closest('pre'); + const hasButtons = !code.classList.contains('no-buttons'); + const isOpen = code.classList.contains('open') || !hasButtons; + const noEdit = code.classList.contains('no-edit'); + const id = `code-example-${uuid().slice(-12)}`; + let preview = pre.textContent; - if (Object.keys(localOptions.attributes).length > 0) { - // attributes only work on the new syntax - localOptions.new = true; - } + // Run preview scripts as modules to prevent collisions + const root = parse(preview, { blockTextElements: { script: true } }); + root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module')); + preview = root.toString(); - if (localOptions.open === undefined) { - if (localOptions.defaultOpen === true) { - localOptions.open = localOptions.defaultOpen; - } else if (typeof localOptions.defaultOpen === 'function') { - localOptions.open = localOptions.defaultOpen(code, { - pre, - inputPathIndex: stats.inputPaths[inputPath], - outputPathIndex: stats.outputPaths[outputPath], - }); - } - } + const codeExample = parse(` +
+
+ ${preview} + +
+
+ ${pre.outerHTML} +
+ ${ + hasButtons + ? ` +
+ - const template = localOptions.new ? 'new' : 'old'; - const codeExample = parse(templates[template](pre, code, localOptions)); + ${ + noEdit + ? '' + : ` + + ` + } - pre.replaceWith(codeExample); + ` + : '' + } +
+
+ `); + + pre.replaceWith(codeExample); + }); + + return doc.toString(); }); - - return doc.toString(); - }); + }; } diff --git a/docs/assets/examples/page/demo-1.html b/docs/assets/examples/page/demo-1.html new file mode 100644 index 000000000..603043cd7 --- /dev/null +++ b/docs/assets/examples/page/demo-1.html @@ -0,0 +1,287 @@ + + + + + + Web Awesome Page Demo 1 + + + + + + + + + + + + +
+ +
+
+

Identification

+

+ Lorem ipsum odor amet, consectetuer adipiscing elit. Eget habitant scelerisque lectus ultrices nascetur + aliquet sapien primis. Cursus sapien fusce semper nulla elit sociosqu lectus per sem. Sem ad porttitor dictum + nisl pharetra tortor convallis. Sit molestie hendrerit porta dictum tortor posuere euismod magna. Mauris + suspendisse pharetra finibus; eleifend etiam ridiculus. +

+

Range and Habitat

+

+ Diam sed ipsum pretium porttitor class cubilia elementum. Blandit felis ligula habitant ultricies vulputate + rutrum lacus commodo pulvinar. Nostra semper placerat lectus in dis eu. Sagittis ipsum placerat rhoncus lacus + id eget. Erat pharetra aptent enim, augue accumsan ultricies inceptos habitasse. Senectus id maximus + parturient tellus; fermentum posuere vulputate luctus. Ac tempus dapibus vehicula ligula ullamcorper sit duis. +

+

Behavior

+

+ Erat vitae luctus arcu taciti malesuada pretium arcu justo primis. Cubilia vitae maecenas congue velit id + netus arcu. Dictum vel pellentesque taciti fermentum risus consectetur amet. Faucibus commodo habitasse sem + maximus praesent purus, dignissim tristique porta. Platea magna justo ipsum ut metus ac facilisi. Imperdiet + laoreet pharetra maximus lacus tortor suscipit. Nam quisque iaculis orci porttitor pellentesque rhoncus. + Molestie sagittis tincidunt quisque nisi non urna conubia. +

+

Conservation

+

+ Nullam magna quam quisque eu varius integer. Inceptos donec facilisi risus himenaeos semper mollis habitasse. + Vehicula lacus vivamus euismod pharetra mollis dictum. Ante ex tortor elementum eleifend habitasse orci + aliquam. Fames erat senectus fames etiam dapibus cursus. +

+
+
+
+ + +
+
+ + +
+ + + + + + diff --git a/docs/assets/examples/page/demo-2.html b/docs/assets/examples/page/demo-2.html new file mode 100644 index 000000000..b3e63e96d --- /dev/null +++ b/docs/assets/examples/page/demo-2.html @@ -0,0 +1,445 @@ + + + + + + Web Awesome Page Demo 2 + + + + + + + + + + + + diff --git a/docs/assets/scripts/code-examples.js b/docs/assets/scripts/code-examples.js new file mode 100644 index 000000000..eec5ba1bb --- /dev/null +++ b/docs/assets/scripts/code-examples.js @@ -0,0 +1,98 @@ +// +// Resizing previews +// +document.addEventListener('mousedown', handleResizerDrag); +document.addEventListener('touchstart', handleResizerDrag, { passive: true }); + +function handleResizerDrag(event) { + const resizer = event.target.closest('.code-example-resizer'); + const preview = event.target.closest('.code-example-preview'); + + if (!resizer || !preview) return; + + let startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX; + let startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10); + + event.preventDefault(); + preview.classList.add('code-example-preview--dragging'); + document.documentElement.addEventListener('mousemove', dragMove); + document.documentElement.addEventListener('touchmove', dragMove); + document.documentElement.addEventListener('mouseup', dragStop); + document.documentElement.addEventListener('touchend', dragStop); + + function dragMove(event) { + const width = startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX; + preview.style.width = `${width}px`; + } + + function dragStop() { + preview.classList.remove('code-example-preview--dragging'); + document.documentElement.removeEventListener('mousemove', dragMove); + document.documentElement.removeEventListener('touchmove', dragMove); + document.documentElement.removeEventListener('mouseup', dragStop); + document.documentElement.removeEventListener('touchend', dragStop); + } +} + +// +// Toggle source and CodePen functionality +// +document.addEventListener('click', event => { + const toggle = event.target?.closest('.code-example-toggle'); + const pen = event.target?.closest('.code-example-pen'); + + // Toggle source + if (toggle) { + const codeExample = toggle.closest('.code-example'); + const isOpen = !codeExample.classList.contains('open'); + + toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + codeExample.classList.toggle('open', isOpen); + } + + // Edit in CodePen + if (pen) { + const codeExample = pen.closest('.code-example'); + const code = codeExample.querySelector('code'); + const cdnUrl = document.documentElement.dataset.cdnUrl; + const html = + `\n` + + `\n` + + `\n` + + `\n\n` + + `${code.textContent}`; + const css = 'html > body {\n padding: 2rem !important;\n}'; + const js = ''; + + const form = document.createElement('form'); + form.action = 'https://codepen.io/pen/define'; + form.method = 'POST'; + form.target = '_blank'; + + const data = { + title: '', + description: '', + tags: ['webawesome'], + editors: '1000', + head: '', + html_classes: '', + css_external: '', + js_external: '', + js_module: true, + js_pre_processor: 'none', + html, + css, + js, + }; + + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'data'; + input.value = JSON.stringify(data); + form.append(input); + + document.documentElement.append(form); + form.submit(); + form.remove(); + } +}); diff --git a/docs/assets/styles/cera_typeface.css b/docs/assets/styles/cera-typeface.css similarity index 100% rename from docs/assets/styles/cera_typeface.css rename to docs/assets/styles/cera-typeface.css diff --git a/docs/assets/styles/code-examples.css b/docs/assets/styles/code-examples.css new file mode 100644 index 000000000..54e92c1a7 --- /dev/null +++ b/docs/assets/styles/code-examples.css @@ -0,0 +1,151 @@ +.code-example { + background: var(--wa-color-surface-lowered); + border: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet); + border-radius: var(--wa-border-radius-l); + color: var(--wa-color-text-normal); + margin-block-end: var(--wa-flow-spacing); + isolation: isolate; +} + +.code-example-preview { + position: relative; + background: var(--wa-color-surface-default); + border-top-left-radius: inherit; + border-top-right-radius: inherit; + border-bottom: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet); + padding: 2rem 3.25rem 2rem 2rem; + min-width: 20rem; + max-width: 100%; + overflow: hidden; + + > :first-child { + margin-block-start: 0; + } + + > :last-child { + margin-block-end: 0; + } +} + +/* Block the preview while dragging to prevent iframes from intercepting drag events */ +.code-example-preview--dragging:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + cursor: ew-resize; +} + +.code-example-resizer { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 1.75rem; + font-size: 20px; + color: var(--wa-color-text-quiet); + background-color: var(--wa-color-neutral-0, white); + border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet); + border-top-right-radius: var(--wa-border-radius-l); + cursor: ew-resize; + + wa-icon { + font-size: 1rem; + } +} + +@media screen and (max-width: 600px) { + .code-example-preview { + padding-right: 2rem; + } + + .code-example-resizer { + display: none; + } +} + +.code-example-source { + border-bottom: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet); +} + +.code-example:not(.open) .code-example-source { + display: none; +} + +.code-example.open .code-example-toggle wa-icon { + rotate: 180deg; +} + +.code-example-source pre { + position: relative; + border-radius: 0; + margin: 0; + white-space: normal; +} + +.code-example-source:not(:has(+ .code-example-buttons)) { + border-bottom: none; + + pre { + border-bottom-right-radius: var(--wa-border-radius-l); + border-bottom-left-radius: var(--wa-border-radius-l); + } +} + +.code-example-buttons { + display: flex; + align-items: stretch; + background: var(--wa-color-surface-default) !important; /* TODO - remove after native styles refactor */ + color: var(--wa-color-text-quiet) !important; /* TODO - remove after native styles refactor */ + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + + button { + position: relative; + all: unset; + flex: 1 0 auto; + font-size: 0.875rem; + color: var(--wa-color-text-quiet); + border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet); + text-align: center; + padding: 0.5rem; + cursor: pointer; + + &:hover { + border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet) !important; /* TODO - remove after native styles refactor */ + background: var(--wa-color-surface-default) !important; /* TODO - remove after native styles refactor */ + color: var(--wa-color-text-quiet) !important; /* TODO - remove after native styles refactor */ + } + + &:first-of-type { + border-left: none; + border-bottom-left-radius: var(--wa-border-radius-l); + } + + &:last-of-type { + border-bottom-right-radius: var(--wa-border-radius-l); + } + + &:focus-visible { + z-index: 3; + outline: var(--wa-focus-ring); + } + } + + .code-example-pen { + flex: 0 0 100px; + white-space: nowrap; + } + + wa-icon { + width: 1em; + height: 1em; + vertical-align: -2px; + } +} diff --git a/docs/assets/styles/docs.css b/docs/assets/styles/docs.css index da58cd0f6..9ba02ef69 100644 --- a/docs/assets/styles/docs.css +++ b/docs/assets/styles/docs.css @@ -1,8 +1,9 @@ +@import 'code-examples.css'; @import 'code-highlighter.css'; @import 'copy-code.css'; @import 'outline.css'; @import 'search.css'; -@import 'cera_typeface.css'; +@import 'cera-typeface.css'; @import 'theme-icons.css'; :root { diff --git a/docs/docs/components/code-demo.md b/docs/docs/components/code-demo.md deleted file mode 100644 index d53a55e92..000000000 --- a/docs/docs/components/code-demo.md +++ /dev/null @@ -1,213 +0,0 @@ ---- -title: Code Demo -description: Code demos can be used to render code examples as inline live demos. -tags: component -isPro: true -unpublished: true ---- - -```html {.example} - -

-    <button>Click me!</button>
-    <wa-button>Click me!</wa-button>
-  
-
-``` - -This component is used right here in the docs to render code examples. - -:::warning -Do not render untrusted content in a `` element. This component renders the content as HTML, which introduces XSS vulnerabilities if used with untrusted content. -::: - -## Examples - -### Open by default - -```html {.example} - -

-    <button>Click me!</button>
-    <wa-button>Click me!</wa-button>
-  
-
-``` - -### Custom previews - -In some cases you may want to preprocess the code displayed, for example to sanitize HTML, remove irrelevant elements or attributes, fix whitespace, or do server-side rendering (SSR). -For these cases, you can slot in a custom preview: - -```html {.example} - - Click me! -

-    <button>Click me!</button>
-  
-
-``` - -Note that this means the preview will be in the light DOM, and can conflict with other things on the page. -To only render the custom preview within the component’s shadow DOM, or to display raw text, you can wrap it in a `