From a0e7554a5ac36200bf97a20732dea077016afbfb Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 8 Oct 2024 13:38:32 +0300 Subject: [PATCH 01/22] docs: reduce number of file watchers --- docs/_plugins/rhds.cjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/_plugins/rhds.cjs b/docs/_plugins/rhds.cjs index 1b384b2bad..d5c8223ef2 100644 --- a/docs/_plugins/rhds.cjs +++ b/docs/_plugins/rhds.cjs @@ -368,9 +368,6 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { } }); - eleventyConfig.addWatchTarget('docs/patterns/**/patterns/*.html'); - eleventyConfig.addWatchTarget('docs/theming/**/patterns/*.html'); - for (const tagName of fs.readdirSync(path.join(process.cwd(), './elements/'))) { const dir = path.join(process.cwd(), './elements/', tagName, 'docs/'); eleventyConfig.addWatchTarget(dir); From d722a040c34eaa4ba49b1877720ac8028f6faadd Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 8 Oct 2024 13:37:24 +0300 Subject: [PATCH 02/22] docs: reimplement lit-ssr plugin enable ssr for uxdot-pattern --- declaration.d.ts | 8 ++ docs/_plugins/lit-ssr/lit.cjs | 123 +++++++++++++++++++++++ docs/_plugins/lit-ssr/worker/worker.js | 59 +++++++++++ eleventy.config.cjs | 9 +- patches/@lit+context+1.1.2.patch | 11 ++ patches/@patternfly+pfe-core+4.0.2.patch | 35 ++++++- 6 files changed, 237 insertions(+), 8 deletions(-) create mode 100644 docs/_plugins/lit-ssr/lit.cjs create mode 100644 docs/_plugins/lit-ssr/worker/worker.js create mode 100644 patches/@lit+context+1.1.2.patch diff --git a/declaration.d.ts b/declaration.d.ts index f8afc5e132..c04475a345 100644 --- a/declaration.d.ts +++ b/declaration.d.ts @@ -7,3 +7,11 @@ declare module 'prism-esm/plugins/line-numbers/prism-line-numbers.js' { import type { Prism } from "prism-esm"; export function Plugin(prism: Prism): void } + +declare module '@11ty/eleventy-plugin-syntaxhighlight/src/HighlightPairedShortcode.js' { + export default function HighlightPairedShortcode(content: string, language: string, highlightLines: string, options: object): any +} + +declare module '@11ty/eleventy-plugin-syntaxhighlight/src/getAttributes.js' { + export default function getAttributes(...args: any[]): string +} diff --git a/docs/_plugins/lit-ssr/lit.cjs b/docs/_plugins/lit-ssr/lit.cjs new file mode 100644 index 0000000000..bd9ce51160 --- /dev/null +++ b/docs/_plugins/lit-ssr/lit.cjs @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +const path = require('node:path'); +const { pathToFileURL } = require('node:url'); +// eslint-disable-next-line no-redeclare +const { Worker } = require('node:worker_threads'); + +module.exports = { + /** + * @param {import('@11ty/eleventy').UserConfig} eleventyConfig + * @param {{componentModules: string[]}} resolvedComponentModules + */ + configFunction( + eleventyConfig, + { componentModules } = {} + ) { + if (componentModules === undefined || componentModules.length === 0) { + // If there are no component modules, we could never have anything to + // render. + return; + } + + const resolvedComponentModules = componentModules.map( + module => pathToFileURL(path.resolve(process.cwd(), module)).href + ); + + let worker; + + const requestIdResolveMap = new Map(); + let requestId = 0; + + eleventyConfig.on('eleventy.before', async () => { + worker = new Worker(path.resolve(__dirname, './worker/worker.js')); + + worker.on('error', err => { + // eslint-disable-next-line no-console + console.error( + 'Unexpected error while rendering lit component in worker thread', + err + ); + throw err; + }); + + let requestResolve; + const requestPromise = new Promise(resolve => { + requestResolve = resolve; + }); + + worker.on('message', message => { + switch (message.type) { + case 'initialize-response': { + requestResolve(); + break; + } + + case 'render-response': { + const { id, rendered } = message; + const resolve = requestIdResolveMap.get(id); + if (resolve === undefined) { + throw new Error( + '@lit-labs/eleventy-plugin-lit received invalid render-response message' + ); + } + resolve(rendered); + requestIdResolveMap.delete(id); + break; + } + } + }); + + const message = { + type: 'initialize-request', + imports: resolvedComponentModules, + }; + + worker.postMessage(message); + await requestPromise; + }); + + eleventyConfig.on('eleventy.after', async () => { + await worker.terminate(); + }); + + eleventyConfig.addTransform('render-lit', async function(content) { + if (!this.page.outputPath.endsWith('.html')) { + return content; + } + + const renderedContent = await new Promise(resolve => { + requestIdResolveMap.set(requestId, resolve); + const message = { + type: 'render-request', + id: requestId++, + content, + page: JSON.parse(JSON.stringify(this.page)), + }; + worker.postMessage(message); + }); + + const outerMarkersTrimmed = trimOuterMarkers(renderedContent); + return outerMarkersTrimmed; + } + ); + }, +}; + +// Lit SSR includes comment markers to track the outer template from +// the template we've generated here, but it's not possible for this +// outer template to be hydrated, so they serve no purpose. + +// TODO(aomarks) Maybe we should provide an option to SSR option to skip +// outer markers (though note there are 2 layers of markers due to the +// use of the unsafeHTML directive). +function trimOuterMarkers(renderedContent) { + return renderedContent + .replace(/^(()|(<\?>)|\s)+/, '') + .replace(/(()|(<\?>)|\s)+$/, ''); +} + diff --git a/docs/_plugins/lit-ssr/worker/worker.js b/docs/_plugins/lit-ssr/worker/worker.js new file mode 100644 index 0000000000..254ccec445 --- /dev/null +++ b/docs/_plugins/lit-ssr/worker/worker.js @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +import { parentPort } from 'worker_threads'; +import { render } from '@lit-labs/ssr'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { collectResult } from '@lit-labs/ssr/lib/render-result.js'; + +import { LitElementRenderer } from '@lit-labs/ssr/lib/lit-element-renderer.js'; + +if (parentPort === null) { + throw new Error('worker.js must only be run in a worker thread'); +} + +let initialized = false; + +parentPort.on('message', async message => { + switch (message.type) { + case 'initialize-request': { + if (!initialized) { + const { imports } = message; + await Promise.all(imports.map(module => import(module))); + const response = { type: 'initialize-response' }; + parentPort.postMessage(response); + } + initialized = true; + break; + } + + case 'render-request': { + const { id, content, page } = message; + const result = render(unsafeHTML(content), { + elementRenderers: [ + class UxdotPatternRenderer extends LitElementRenderer { + * renderShadow(renderInfo) { + if (this.tagName === 'uxdot-pattern') { + this.element.ssrController.page = page; + yield [this.element.ssrController.hostUpdate()]; + } + yield* super.renderShadow(renderInfo); + } + }, + ], + }); + const rendered = await collectResult(result); + const response = { + type: 'render-response', + id, + rendered, + }; + parentPort.postMessage(response); + break; + } + } +}); + diff --git a/eleventy.config.cjs b/eleventy.config.cjs index cb04446a57..de17e7b89c 100644 --- a/eleventy.config.cjs +++ b/eleventy.config.cjs @@ -11,7 +11,7 @@ const RHDSPlugin = require('./docs/_plugins/rhds.cjs'); const DesignTokensPlugin = require('./docs/_plugins/tokens.cjs'); const RHDSMarkdownItPlugin = require('./docs/_plugins/markdown-it.cjs'); const ImportMapPlugin = require('./docs/_plugins/importMap.cjs'); -const LitPlugin = require('@lit-labs/eleventy-plugin-lit'); +const LitPlugin = require('./docs/_plugins/lit-ssr/lit.cjs'); const HelmetPlugin = require('eleventy-plugin-helmet'); const util = require('node:util'); @@ -34,7 +34,6 @@ module.exports = function(eleventyConfig) { await exec('npx tspc'); }); - eleventyConfig.addWatchTarget('docs/patterns/**/*.html'); eleventyConfig.watchIgnores?.add('docs/assets/redhat/'); eleventyConfig.watchIgnores?.add('**/*.spec.ts'); eleventyConfig.watchIgnores?.add('**/*.d.ts'); @@ -48,8 +47,6 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPassthroughCopy('docs/robots.txt'); eleventyConfig.addPassthroughCopy('docs/assets/**/*'); eleventyConfig.addPassthroughCopy('docs/styles/**/*'); - eleventyConfig.addPassthroughCopy('docs/patterns/**/*.css'); - eleventyConfig.addPassthroughCopy('docs/theming/**/*.css'); eleventyConfig.addPassthroughCopy('docs/foundations/**/*.{css,js}'); @@ -59,7 +56,6 @@ module.exports = function(eleventyConfig) { }); } - eleventyConfig.addWatchTarget('docs/patterns/**/*.(html|md)'); eleventyConfig.addWatchTarget('docs/styles/'); eleventyConfig.addGlobalData('isLocal', isLocal); @@ -197,7 +193,6 @@ module.exports = function(eleventyConfig) { }); eleventyConfig.addPlugin(LitPlugin, { - mode: 'worker', componentModules: [ 'docs/assets/javascript/elements/uxdot-masthead.js', 'docs/assets/javascript/elements/uxdot-header.js', @@ -210,7 +205,7 @@ module.exports = function(eleventyConfig) { 'docs/assets/javascript/elements/uxdot-best-practice.js', 'docs/assets/javascript/elements/uxdot-search.js', 'docs/assets/javascript/elements/uxdot-toc.js', - // 'docs/assets/javascript/elements/uxdot-pattern.js', + 'docs/assets/javascript/elements/uxdot-pattern.js', // Uses context API need to work around issues // 'docs/assets/javascript/elements/uxdot-example.js', // extends RhTabs so cant DSD yet diff --git a/patches/@lit+context+1.1.2.patch b/patches/@lit+context+1.1.2.patch new file mode 100644 index 0000000000..4db5348a54 --- /dev/null +++ b/patches/@lit+context+1.1.2.patch @@ -0,0 +1,11 @@ +diff --git a/node_modules/@lit/context/lib/controllers/context-provider.js b/node_modules/@lit/context/lib/controllers/context-provider.js +index 4a2c228..bab057f 100644 +--- a/node_modules/@lit/context/lib/controllers/context-provider.js ++++ b/node_modules/@lit/context/lib/controllers/context-provider.js +@@ -3,5 +3,5 @@ import{ContextRequestEvent as t}from"../context-request-event.js";import{ValueNo + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause +- */class e extends Event{constructor(t){super("context-provider",{bubbles:!0,composed:!0}),this.context=t}}class i extends s{constructor(s,e,i){super(void 0!==e.context?e.initialValue:i),this.onContextRequest=t=>{const s=t.composedPath()[0];t.context===this.context&&s!==this.host&&(t.stopPropagation(),this.addCallback(t.callback,s,t.subscribe))},this.onProviderRequest=s=>{const e=s.composedPath()[0];if(s.context!==this.context||e===this.host)return;const i=new Set;for(const[s,{consumerHost:e}]of this.subscriptions)i.has(s)||(i.add(s),e.dispatchEvent(new t(this.context,s,!0)));s.stopPropagation()},this.host=s,void 0!==e.context?this.context=e.context:this.context=e,this.attachListeners(),this.host.addController?.(this)}attachListeners(){this.host.addEventListener("context-request",this.onContextRequest),this.host.addEventListener("context-provider",this.onProviderRequest)}hostConnected(){this.host.dispatchEvent(new e(this.context))}}export{i as ContextProvider,e as ContextProviderEvent}; ++ */class e extends Event{constructor(t){super("context-provider",{bubbles:!0,composed:!0}),this.context=t}}class i extends s{constructor(s,e,i){super(void 0!==e.context?e.initialValue:i),this.onContextRequest=t=>{const s=t.composedPath()[0];t.context===this.context&&s!==this.host&&(t.stopPropagation(),this.addCallback(t.callback,s,t.subscribe))},this.onProviderRequest=s=>{const e=s.composedPath()[0];if(s.context!==this.context||e===this.host)return;const i=new Set;for(const[s,{consumerHost:e}]of this.subscriptions)i.has(s)||(i.add(s),e.dispatchEvent(new t(this.context,s,!0)));s.stopPropagation()},this.host=s,void 0!==e.context?this.context=e.context:this.context=e,this.attachListeners(),this.host.addController?.(this)}attachListeners(){this.host.addEventListener?.("context-request",this.onContextRequest),this.host.addEventListener?.("context-provider",this.onProviderRequest)}hostConnected(){this.host.dispatchEvent(new e(this.context))}}export{i as ContextProvider,e as ContextProviderEvent}; + //# sourceMappingURL=context-provider.js.map diff --git a/patches/@patternfly+pfe-core+4.0.2.patch b/patches/@patternfly+pfe-core+4.0.2.patch index 0d147a66fc..f5f330344c 100644 --- a/patches/@patternfly+pfe-core+4.0.2.patch +++ b/patches/@patternfly+pfe-core+4.0.2.patch @@ -1,5 +1,38 @@ +diff --git a/node_modules/@patternfly/pfe-core/controllers/tabs-aria-controller.js b/node_modules/@patternfly/pfe-core/controllers/tabs-aria-controller.js +index 0993544..f56cab0 100644 +--- a/node_modules/@patternfly/pfe-core/controllers/tabs-aria-controller.js ++++ b/node_modules/@patternfly/pfe-core/controllers/tabs-aria-controller.js +@@ -1,3 +1,4 @@ ++import { isServer } from 'lit'; + var _TabsAriaController_instances, _TabsAriaController_logger, _TabsAriaController_host, _TabsAriaController_element, _TabsAriaController_tabPanelMap, _TabsAriaController_options, _TabsAriaController_mo, _TabsAriaController_onSlotchange; + import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib"; + import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; +@@ -29,19 +30,19 @@ export class TabsAriaController { + _TabsAriaController_mo.set(this, new MutationObserver(__classPrivateFieldGet(this, _TabsAriaController_instances, "m", _TabsAriaController_onSlotchange).bind(this))); + __classPrivateFieldSet(this, _TabsAriaController_options, options, "f"); + __classPrivateFieldSet(this, _TabsAriaController_logger, new Logger(host), "f"); +- if (host instanceof HTMLElement) { ++ if (host instanceof (globalThis.HTMLElement ?? class {})) { + __classPrivateFieldSet(this, _TabsAriaController_element, host, "f"); + } + else { + const element = options.getHTMLElement?.(); +- if (!element) { ++ if (!element && !isServer) { + throw new Error('TabsController must be instantiated with an HTMLElement or a `getHTMLElement()` option'); + } + __classPrivateFieldSet(this, _TabsAriaController_element, element, "f"); + } + (__classPrivateFieldSet(this, _TabsAriaController_host, host, "f")).addController(this); +- __classPrivateFieldGet(this, _TabsAriaController_element, "f").addEventListener('slotchange', __classPrivateFieldGet(this, _TabsAriaController_instances, "m", _TabsAriaController_onSlotchange)); +- if (__classPrivateFieldGet(this, _TabsAriaController_element, "f").isConnected) { ++ __classPrivateFieldGet(this, _TabsAriaController_element, "f")?.addEventListener('slotchange', __classPrivateFieldGet(this, _TabsAriaController_instances, "m", _TabsAriaController_onSlotchange)); ++ if (__classPrivateFieldGet(this, _TabsAriaController_element, "f")?.isConnected) { + this.hostConnected(); + } + } diff --git a/node_modules/@patternfly/pfe-core/ssr-shims.js b/node_modules/@patternfly/pfe-core/ssr-shims.js -index d432769..c7a30fb 100644 +index d432769..c8fe51d 100644 --- a/node_modules/@patternfly/pfe-core/ssr-shims.js +++ b/node_modules/@patternfly/pfe-core/ssr-shims.js @@ -42,6 +42,8 @@ globalThis.IntersectionObserver ?? (globalThis.IntersectionObserver = ObserverSh From a19e2a48acbf7ef26778811b0f9499f2a27dcac4 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 8 Oct 2024 13:38:55 +0300 Subject: [PATCH 03/22] docs: implement ssr-able uxdot-pattern --- declaration.d.ts | 1 + docs/_plugins/shortcodes.cjs | 2 - docs/_plugins/shortcodes/uxdotPattern.cjs | 113 ----------------- .../uxdot-pattern-ssr-controller-client.ts | 16 +++ .../uxdot-pattern-ssr-controller-server.ts | 109 ++++++++++++++++ .../elements/uxdot-pattern-ssr-controller.ts | 8 ++ .../javascript/elements/uxdot-pattern.css | 117 +++++++++--------- .../javascript/elements/uxdot-pattern.ts | 115 ++++++++++------- .../javascript/elements/uxdot-sidenav.js.mapw | 1 + 9 files changed, 263 insertions(+), 219 deletions(-) delete mode 100644 docs/_plugins/shortcodes/uxdotPattern.cjs create mode 100644 docs/assets/javascript/elements/uxdot-pattern-ssr-controller-client.ts create mode 100644 docs/assets/javascript/elements/uxdot-pattern-ssr-controller-server.ts create mode 100644 docs/assets/javascript/elements/uxdot-pattern-ssr-controller.ts create mode 100644 docs/assets/javascript/elements/uxdot-sidenav.js.mapw diff --git a/declaration.d.ts b/declaration.d.ts index c04475a345..a202e6ff21 100644 --- a/declaration.d.ts +++ b/declaration.d.ts @@ -15,3 +15,4 @@ declare module '@11ty/eleventy-plugin-syntaxhighlight/src/HighlightPairedShortco declare module '@11ty/eleventy-plugin-syntaxhighlight/src/getAttributes.js' { export default function getAttributes(...args: any[]): string } + diff --git a/docs/_plugins/shortcodes.cjs b/docs/_plugins/shortcodes.cjs index 539d15628d..85c70347a2 100644 --- a/docs/_plugins/shortcodes.cjs +++ b/docs/_plugins/shortcodes.cjs @@ -7,7 +7,6 @@ const RenderInstallation = require('./shortcodes/renderInstallation.cjs'); const RenderLightDom = require('./shortcodes/renderLightDom.cjs'); const RenderCodeDocs = require('./shortcodes/renderCodeDocs.cjs'); const SpacerTokensTable = require('./shortcodes/spacerTokensTable.cjs'); -const UxdotPattern = require('./shortcodes/uxdotPattern.cjs'); module.exports = function(eleventyConfig) { eleventyConfig.addPlugin(RepoStatusList); @@ -17,5 +16,4 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPlugin(RenderLightDom); eleventyConfig.addPlugin(SpacerTokensTable); eleventyConfig.addPlugin(RenderCodeDocs); - eleventyConfig.addPlugin(UxdotPattern); }; diff --git a/docs/_plugins/shortcodes/uxdotPattern.cjs b/docs/_plugins/shortcodes/uxdotPattern.cjs deleted file mode 100644 index 0f5b537741..0000000000 --- a/docs/_plugins/shortcodes/uxdotPattern.cjs +++ /dev/null @@ -1,113 +0,0 @@ -const { readFile } = require('node:fs/promises'); -const { join } = require('node:path'); -const { pathToFileURL } = require('node:url'); - -// for editor highlighting -const html = String.raw; - -// because markdown, we have to replace newlines with a cookie crumb -const COMMENT = ''; - -/** - * Returns a string with common indent stripped from each line. Useful for templating HTML - * @param {string} str - */ -function dedent(str) { - const stripped = str.replace(/^\n/, ''); - const match = stripped.match(/^\s+/); - return match ? stripped.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str; -} - -async function getCss(partial, kwargs) { - const Tools = await import('@parse5/tools'); - let cssContent = kwargs.css ? await readFile(new URL(kwargs.css, kwargs.baseUrl), 'utf-8') : ''; - for (const styleTag of Tools.queryAll(partial, node => - Tools.isElementNode(node) && node.tagName === 'style')) { - cssContent += `\n${dedent(Tools.getTextContent(styleTag))}`; - Tools.removeNode(styleTag); - } - return cssContent.trim(); -} - -/** - * **LF** - * **LF** - */ -const NEWLINE_RE = /\n\n/gm; - -const dash = s => s.replace(/([A-Z])/g, '-$1'); - -const attrMap = attrs => Object.entries(attrs) - .map(([name, value]) => `${value ? dash(name) : ''}${value === true || value === false ? '' : `="${value}"`}`) - .join(' '); - -/** @param {import('@11ty/eleventy').UserConfig} eleventyConfig */ -module.exports = function(eleventyConfig) { - eleventyConfig.addPairedShortcode('uxdotPattern', async function(_content, kwargs = {}) { - const { __keywords, src, js, fullHeight, ...patternArgs } = kwargs; - if ('allow' in patternArgs) { - const allowed = patternArgs.allow.split(',').map(x => x.trim()); - patternArgs.colorPalette ??= 'lightest'; - while (allowed.length && !allowed.includes(patternArgs.colorPalette)) { - patternArgs.colorPalette = allowed.shift(); - } - } - const { parseFragment, serialize } = await import('parse5'); - const content = !src ? _content : await readFile(join(process.cwd(), src), 'utf8'); - const partial = parseFragment(content); - const baseUrl = pathToFileURL(this.page.inputPath); - const cssContent = await getCss(partial, { ...kwargs, baseUrl }); - const jsContent = js && await readFile(new URL(js, baseUrl), 'utf-8'); - return html` - -

Example

- ${content} -

HTML

- - Copy to Clipboard - - - ${!cssContent ? '' : html` -

CSS

- - Copy to Clipboard - - - `}${!jsContent ? '' : html` -

JavaScript

- - Copy to Clipboard - - `} - -
-`; - }); - - eleventyConfig.addTransform('uxdot-pattern-restore-newlines', async function(content) { - const { parse, serialize } = await import('parse5'); - const { - queryAll, - isElementNode, - isTextNode, - getTextContent, - setTextContent, - } = await import('@parse5/tools'); - const document = parse(content); - const isUxDotPattern = node => - isElementNode(node) - && node.tagName === 'uxdot-pattern'; - for (const pattern of queryAll(document, isUxDotPattern)) { - for (const node of queryAll(pattern, isTextNode)) { - setTextContent(node, getTextContent(node).replaceAll(COMMENT, '\n\n')); - } - } - return serialize(document); - }); -}; diff --git a/docs/assets/javascript/elements/uxdot-pattern-ssr-controller-client.ts b/docs/assets/javascript/elements/uxdot-pattern-ssr-controller-client.ts new file mode 100644 index 0000000000..fc78d99aaa --- /dev/null +++ b/docs/assets/javascript/elements/uxdot-pattern-ssr-controller-client.ts @@ -0,0 +1,16 @@ +import type { UxdotPattern } from './uxdot-pattern.js'; + +export class UxdotPatternSSRControllerClient { + constructor(public host: UxdotPattern) { } + allContent?: Node; + htmlContent?: Node; + jsContent?: Node; + cssContent?: Node; + hasCss = false; + hasJs = false; + + hostUpdate() { + void 0; + } +} + diff --git a/docs/assets/javascript/elements/uxdot-pattern-ssr-controller-server.ts b/docs/assets/javascript/elements/uxdot-pattern-ssr-controller-server.ts new file mode 100644 index 0000000000..0c6368ecbd --- /dev/null +++ b/docs/assets/javascript/elements/uxdot-pattern-ssr-controller-server.ts @@ -0,0 +1,109 @@ +import { readFile } from 'node:fs/promises'; +import { join, dirname } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { parseFragment, serialize } from 'parse5'; +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; +import * as Tools from '@parse5/tools'; +import type { UxdotPattern } from './uxdot-pattern.js'; +import type { DirectiveResult } from 'lit/directive.js'; + +function dedent(str: string) { + const stripped = str.replace(/^\n/, ''); + const match = stripped.match(/^\s+/); + return match ? stripped.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str; +} + +interface EleventyPageData { + inputPath: string; + outputPath: string; +} + +export class UxdotPatternSSRControllerServer { + constructor(public host: UxdotPattern) {} + + #HighlightPairedShortcode?: ( + content: string, + language: string, + highlightlines: string, + options: object, + ) => string; + + allContent?: DirectiveResult; + htmlContent?: DirectiveResult; + cssContent?: DirectiveResult; + jsContent?: DirectiveResult; + hasCss = false; + hasJs = false; + // this is set in the worker + page!: EleventyPageData; + + + async #extractInlineContent(kind: 'js' | 'css', partial: Tools.Node) { + const prop = kind === 'js' ? 'jsSrc' as const : 'cssSrc' as const; + const nodePred = kind === 'js' ? (node: Tools.Element) => node.tagName === 'script' + : kind === 'css' ? (node: Tools.Element) => node.tagName === 'style' + : () => false; + const baseUrl = pathToFileURL(this.page.inputPath); + let content = !this.host[prop] ? '' + : await readFile(new URL(this.host[prop], baseUrl.href), 'utf-8'); + for (const scriptTag of Tools.queryAll(partial, node => + Tools.isElementNode(node) && nodePred(node))) { + content += `\n${dedent(Tools.getTextContent(scriptTag))}`; + Tools.removeNode(scriptTag); + } + return content.trim(); + } + + async #getPatternContent() { + const { src } = this.host; + if (!src) { + return ''; + } else { + return readFile(join(dirname(this.page.inputPath), src), 'utf8'); + } + } + + #highlight(language: string, content: string) { + const result = this.#HighlightPairedShortcode?.(content, language, '', { + lineSeparator: '\n', + errorOnInvalidLanguage: false, + alwaysWrapLineHighlights: false, + preAttributes: {}, + codeAttributes: {}, + }) ?? ''; + return result; + } + + async hostUpdate() { + // START workaround for ssr: prism will try to use the DOM is `document` is available + // but lit-ssr needs the shims, so delete it before highlighting and restore it after + const shim = globalThis.document; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete globalThis.document; + this.#HighlightPairedShortcode = + await import( + '@11ty/eleventy-plugin-syntaxhighlight/src/HighlightPairedShortcode.js', + ).then(m => m.default); + const allContent = await this.#getPatternContent(); + const partial = parseFragment(allContent); + + // NB: the css and js content functions *mutate* the partial, + // so it's important that the HTML content is serialized last, and that + // the entire content is printed as the runtime portion of the pattern. + const cssContent = await this.#extractInlineContent('css', partial); + const jsContent = await this.#extractInlineContent('js', partial); + const htmlContent = serialize(partial).trim(); + + this.hasCss = !!cssContent.length; + this.hasJs = !!jsContent.length; + this.allContent = unsafeHTML(allContent); + this.cssContent = unsafeHTML(this.#highlight('css', cssContent)); + this.jsContent = unsafeHTML(this.#highlight('js', jsContent)); + this.htmlContent = unsafeHTML(this.#highlight('html', htmlContent)); + // END workaround + globalThis.document = shim; + return []; + } +} + diff --git a/docs/assets/javascript/elements/uxdot-pattern-ssr-controller.ts b/docs/assets/javascript/elements/uxdot-pattern-ssr-controller.ts new file mode 100644 index 0000000000..70c26ac4f5 --- /dev/null +++ b/docs/assets/javascript/elements/uxdot-pattern-ssr-controller.ts @@ -0,0 +1,8 @@ +/* eslint-disable @stylistic/max-len */ +import { isServer } from 'lit'; +import type { UxdotPatternSSRControllerServer } from './uxdot-pattern-ssr-controller-server.js'; +import type { UxdotPatternSSRControllerClient } from './uxdot-pattern-ssr-controller-client.js'; + +export const UxdotPatternSSRController: typeof UxdotPatternSSRControllerClient | typeof UxdotPatternSSRControllerServer = + isServer ? await import('./uxdot-pattern-ssr-controller-server.js').then(m => m.UxdotPatternSSRControllerServer) + : await import('./uxdot-pattern-ssr-controller-client.js').then(m => m.UxdotPatternSSRControllerClient); diff --git a/docs/assets/javascript/elements/uxdot-pattern.css b/docs/assets/javascript/elements/uxdot-pattern.css index e4f6456298..6713b6d6ed 100644 --- a/docs/assets/javascript/elements/uxdot-pattern.css +++ b/docs/assets/javascript/elements/uxdot-pattern.css @@ -1,33 +1,58 @@ +@import url('/styles/rh-code-block-lightdom.css'); + :host { - display: block; - container-name: host; - container-type: inline-size; - margin-block-end: var(--rh-space-2xl, 32px); + container: host inline-size; } #container { display: grid; - grid-template-columns: 1fr; - grid-template-areas: 'controls' 'example' 'code'; - gap: var(--rh-space-2xl, 32px) var(--rh-space-4xl, 64px); - padding: var(--rh-space-2xl, 32px); - border: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle); - border-radius: var(--rh-border-radius-default, 3px); + grid-template-columns: auto auto; + grid-template-areas: 'heading' 'controls' 'content' 'code'; + gap: var(--rh-space-2xl) var(--rh-space-4xl); + + @container host (width >= 530px) { + grid-template-areas: 'heading controls' 'content content' 'code code'; + } + + @container host (width >= 992px) { + grid-template-areas: + 'heading heading' + 'controls controls' + 'example example' + 'code code'; + } +} + +#heading { grid-area: heading; } + +h3, +::slotted(h2), +::slotted(h3), +::slotted(h4), +::slotted(h5), +::slotted(h6) { + margin-block: 0 var(--rh-space-lg) !important; + font-size: var(--rh-font-size-body-text-lg) !important; +} + +#content { + display: block; + resize: vertical; + grid-area: content; + padding: var(--rh-space-4xl); + border: var(--rh-border-width-sm) solid var(--rh-color-border-subtle); + border-radius: var(--rh-border-radius-default); background-color: var(--rh-color-surface); color: var(--rh-color-text-primary); border-color: var(--rh-color-border-subtle); - --rh-color-surface: inherit; + @container host (width < 300px) { + padding: var(--rh-space-lg); + } } -#container h3, -#container ::slotted(h2), -#container ::slotted(h3), -#container ::slotted(h4), -#container ::slotted(h5), -#container ::slotted(h6) { - margin-block: 0 var(--rh-space-lg, 16px) !important; - font-size: var(--rh-font-size-body-text-lg, 1.125rem) !important; +#content > * { + margin-inline: auto; } #color-picker { @@ -38,52 +63,22 @@ gap: var(--rh-space-lg, 16px); } -#example { - grid-area: example; -} - -:host([stacked]) #example ::slotted(*) { - max-width: 100%; -} - -#code { +rh-tabs { + border: var(--rh-border-width-sm) solid var(--rh-color-border-subtle); + border-radius: var(--rh-border-radius-default); + max-width: 56rem; /* warning: magic number */ + overflow: hidden; grid-area: code; - display: flex; - flex-direction: column; - gap: var(--rh-space-lg, 16px); -} - -#container.noColorPicker { - grid-template-areas: 'example' 'code'; -} - -#color-picker.noColorPicker { - display: none !important; -} -#example slot[name='heading'] { - display: block; - grid-column: -1/1; -} - -@container host (min-width: 992px) { - #container { - grid-template-columns: max-content auto; - grid-template-areas: - 'controls controls' - 'example code'; - } - - #container.noColorPicker { - grid-template-areas: 'example code'; + & rh-tab-panel { + padding: 0; + border-radius: 0; } - #container.stacked { - grid-template-columns: 1fr; - grid-template-areas: 'controls' 'example' 'code'; - } + & rh-code-block { + --rh-border-radius-default: 0; + --rh-border-width-sm: 0px; - #container.stacked.noColorPicker { - grid-template-areas: 'example' 'code'; + border-width: 0; } } diff --git a/docs/assets/javascript/elements/uxdot-pattern.ts b/docs/assets/javascript/elements/uxdot-pattern.ts index 62444968ae..ac7c411410 100644 --- a/docs/assets/javascript/elements/uxdot-pattern.ts +++ b/docs/assets/javascript/elements/uxdot-pattern.ts @@ -1,9 +1,9 @@ -import { LitElement, html, isServer } from 'lit'; -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; +import { LitElement, html, isServer, type PropertyValues } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; import { classMap } from 'lit/directives/class-map.js'; +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; import { colorContextProvider, @@ -20,9 +20,12 @@ import { paletteNames, } from '@rhds/elements/lib/elements/rh-context-picker/rh-context-picker.js'; +import '@rhds/elements/lib/elements/rh-context-picker/rh-context-picker.js'; import '@rhds/elements/rh-surface/rh-surface.js'; import '@rhds/elements/rh-code-block/rh-code-block.js'; -import '@rhds/elements/lib/elements/rh-context-picker/rh-context-picker.js'; +import '@rhds/elements/rh-tabs/rh-tabs.js'; + +import { UxdotPatternSSRController } from './uxdot-pattern-ssr-controller.js'; import styles from './uxdot-pattern.css'; @@ -36,59 +39,85 @@ export class UxdotPattern extends LitElement { @colorContextConsumer() private on?: ColorTheme; - @property({ reflect: true }) - target = 'container'; + @property({ reflect: true }) src?: string; - @property({ type: Boolean }) - noColorPicker = false; + @property({ attribute: 'output-path' }) outputPath?: string; - @property({ type: Boolean }) - stacked = false; + @property({ attribute: 'input-path' }) inputPath?: string; - @property({ converter: ColorPaletteListConverter }) - allow = paletteNames; + @property({ reflect: true, attribute: 'css-src' }) cssSrc?: string; - #slots = new SlotController(this, 'html', 'css', 'js'); + @property({ reflect: true, attribute: 'js-src' }) jsSrc?: string; - render() { - const { noColorPicker, stacked, on = 'light' } = this; - const hasHtml = this.#slots.hasSlotted('html'); - const hasCss = this.#slots.hasSlotted('css'); - const hasJs = this.#slots.hasSlotted('js'); + @property({ reflect: true }) target = 'content'; + + @property({ type: Boolean }) noColorPicker = false; + + @property({ type: Boolean }) stacked = false; + + @property({ converter: ColorPaletteListConverter }) allow = paletteNames; + + ssrController = new UxdotPatternSSRController(this); + willUpdate() { + if (!isServer && this.shadowRoot && !this.hasUpdated) { + this.ssrController.allContent ||= this.shadowRoot.getElementById('content')!; + this.ssrController.htmlContent ||= this.shadowRoot.querySelector('.language-html')!; + this.ssrController.jsContent ||= this.shadowRoot.querySelector('.language-js')!; + this.ssrController.cssContent ||= this.shadowRoot.querySelector('.language-css')!; + } + } + + render() { const target = isServer || (this.target === 'container') ? this.target : (this.getRootNode() as Document).getElementById(this.target); + const actions = html` + Copy to Clipboard + `; + return html` - -
+
+ + value="${this.colorPalette}" + target="${target}" + allow="${this.allow}"> -
-

Example

- -
-
${!hasHtml ? '' : html` -
-

HTML

- -
`}${!hasCss ? '' : html` -
-

CSS

- -
`}${!hasJs ? '' : html` -
-

JS

- -
`} -
- + +

Example

+ + ${this.ssrController.allContent} + + + HTML + + + ${this.ssrController.htmlContent} + ${actions} + + + CSS + + + ${this.ssrController.cssContent} + ${actions} + + + JS + + + ${this.ssrController.jsContent} + ${actions} + + + +
`; } diff --git a/docs/assets/javascript/elements/uxdot-sidenav.js.mapw b/docs/assets/javascript/elements/uxdot-sidenav.js.mapw new file mode 100644 index 0000000000..33e739cf96 --- /dev/null +++ b/docs/assets/javascript/elements/uxdot-sidenav.js.mapw @@ -0,0 +1 @@ +{"version":3,"file":"uxdot-sidenav.js","sourceRoot":"","sources":["uxdot-sidenav.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEtD,OAAO,mCAAmC,CAAC;;;;;;;AASpC,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAArC;;;QAKuC,SAAI,GAAG,KAAK,CAAC;QAIzD,uCAAsC,IAAI,EAAC;QAE3C,oCAAmC,IAAI,EAAC;IAuH1C,CAAC;IApHC,KAAK,CAAC,iBAAiB;QACrB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAE1B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAA2B,CAAC;QACzD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,uBAAA,IAAI,gCAAmB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,MAAA,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,uBAAA,IAAI,oCAAgB,EAAE,gBAAgB,CAAC,OAAO,EAAE,uBAAA,IAAI,6DAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACjF,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,uBAAA,IAAI,sDAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACzD,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,uBAAA,IAAI,wDAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,uBAAA,IAAI,sDAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,oBAAoB;QAClB,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,uBAAA,IAAI,oCAAgB,EAAE,mBAAmB,CAAC,OAAO,EAAE,uBAAA,IAAI,6DAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,uBAAA,IAAI,sDAAS,CAAC,CAAC;IACrD,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;;;;yBAMU,uBAAA,IAAI,mEAAsB;uBAC5B,IAAI,CAAC,MAAM;;;;;;;;iDAQe,CAAC,IAAI,CAAC,IAAI;KACtD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,uBAAA,IAAI,6BAAgB,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,cAAc,CAAC,IAAI,IAAI,MAAA,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,IAAI;QAC3B,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QACvB,MAAM,IAAI,CAAC,cAAc,CAAC;QAE1B,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,uBAAA,IAAI,iCAAa,EAAE,KAAK,EAAE,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,uBAAA,IAAI,oCAAgB,EAAE,KAAK,EAAE,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IA+CD,KAAK,CAAC,SAAS,GAAG,IAAI;QACpB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACzB,CAAC;;;;;qEAlDe,KAAY;IAC1B,KAAK,CAAC,cAAc,EAAE,CAAC;IACvB,IAAI,CAAC,MAAM,EAAE,CAAC;AAChB,CAAC;uDAEQ,KAAY;IACnB,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;IAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;AACH,CAAC;iFAEqB,KAAoB;IACxC,QAAQ,KAAK,CAAC,GAAG,EAAE,CAAC;QAClB,KAAK,OAAO;YACV,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,OAAO;IACX,CAAC;AACH,CAAC;2DAEU,KAAoB;IAC7B,QAAQ,KAAK,CAAC,GAAG,EAAE,CAAC;QAClB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM;QACR,CAAC;QACD;YACE,MAAM;IACV,CAAC;AACH,CAAC;uDAEQ,KAAoB;IAC3B,QAAQ,KAAK,CAAC,GAAG,EAAE,CAAC;QAClB,KAAK,KAAK;YACR,uBAAA,IAAI,yDAAY,MAAhB,IAAI,EAAa,KAAK,CAAC,CAAC;YACxB,MAAM;QACR;YACE,MAAM;IACV,CAAC;AACH,CAAC;6DASW,KAAoB;IAC9B,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzB,IAAI,MAAM,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAhIM,8BAAiB,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,EAAE,cAAc,EAAE,IAAI,EAAE,AAA5D,CAA6D;AAE9E,mBAAM,GAAG,CAAC,MAAM,CAAC,AAAX,CAAY;AAEmB;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;0CAAc;AAE7C;IAAX,QAAQ,EAAE;6CAAkB;AAPlB,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAkIxB;;AAGM,IAAM,gBAAgB,GAAtB,MAAM,gBAAiB,SAAQ,UAAU;IAAzC;;QAGuC,WAAM,GAAG,KAAK,CAAC;IAU7D,CAAC;IANC,MAAM;QACJ,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACxB,OAAO,IAAI,CAAA;kBACG,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,WAAW,IAAI,CAAC,IAAI;KACrD,CAAC;IACJ,CAAC;;AAXM,uBAAM,GAAG,CAAC,UAAU,CAAC,AAAf,CAAgB;AAEe;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gDAAgB;AAE/C;IAAX,QAAQ,EAAE;8CAAe;AALf,gBAAgB;IAD5B,aAAa,CAAC,oBAAoB,CAAC;GACvB,gBAAgB,CAa5B;;AAGM,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,UAAU;IAA7C;;;QAGuC,aAAQ,GAAG,KAAK,CAAC;IA+B/D,CAAC;IA7BC,iBAAiB;QACf,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,uBAAA,IAAI,sEAAS,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;KAEV,CAAC;IACJ,CAAC;;;gCAED,KAAK,wCAAU,KAAY;IACzB,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,YAAY,iBAAiB,CAAC,EAAE,CAAC;QAC1E,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtE,kEAAkE;QAClE,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE;YAC3C,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE;gBACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,MAAM,EAAE,IAAI;aACb;SACF,CAAC,CAAC,CAAC;IACN,CAAC;AACH,CAAC;AAhCM,2BAAM,GAAG,CAAC,cAAc,CAAC,AAAnB,CAAoB;AAEW;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;sDAAkB;AAHlD,oBAAoB;IADhC,aAAa,CAAC,wBAAwB,CAAC;GAC3B,oBAAoB,CAkChC;;AAGM,IAAM,wBAAwB,GAA9B,MAAM,wBAAyB,SAAQ,UAAU;IAGtD,MAAM;QACJ,OAAO,IAAI,CAAA;;KAEV,CAAC;IACJ,CAAC;;AANM,+BAAM,GAAG,CAAC,kBAAkB,CAAC,AAAvB,CAAwB;AAD1B,wBAAwB;IADpC,aAAa,CAAC,6BAA6B,CAAC;GAChC,wBAAwB,CAQpC;;AAGM,IAAM,4BAA4B,GAAlC,MAAM,4BAA6B,SAAQ,gBAAgB;;AACzD,mCAAM,GAAG,CAAC,UAAU,EAAE,sBAAsB,CAAC,AAAvC,CAAwC;AAD1C,4BAA4B;IADxC,aAAa,CAAC,kCAAkC,CAAC;GACrC,4BAA4B,CAExC","sourcesContent":["import { LitElement, html, isServer } from 'lit';\n\nimport { classMap } from 'lit/directives/class-map.js';\nimport { customElement } from 'lit/decorators/custom-element.js';\nimport { property } from 'lit/decorators/property.js';\n\nimport '@rhds/elements/rh-icon/rh-icon.js';\n\nimport styles from './uxdot-sidenav.css';\nimport itemStyles from './uxdot-sidenav-item.css';\nimport dropdownStyles from './uxdot-sidenav-dropdown.css';\nimport dropdownMenuStyles from './uxdot-sidenav-dropdown-menu.css';\nimport dropdownMenuItemStyles from './uxdot-sidenav-dropdown-menu-item.css';\n\n@customElement('uxdot-sidenav')\nexport class UxdotSideNav extends LitElement {\n static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };\n\n static styles = [styles];\n\n @property({ type: Boolean, reflect: true }) open = false;\n\n @property() trigger?: string;\n\n #triggerElement: HTMLElement | null = null;\n\n #closeButton: HTMLElement | null = null;\n\n\n async connectedCallback() {\n super.connectedCallback();\n\n const root = this.getRootNode() as Document | ShadowRoot;\n if (this.trigger) {\n this.#triggerElement = root.getElementById(this.trigger);\n }\n if (!isServer) {\n this.#triggerElement?.addEventListener('click', this.#onTriggerClick.bind(this));\n this.addEventListener('click', this.#onClick.bind(this));\n this.addEventListener('keydown', this.#onKeydown.bind(this));\n window.addEventListener('keyup', this.#onKeyup.bind(this));\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#triggerElement?.removeEventListener('click', this.#onTriggerClick.bind(this));\n window.removeEventListener('keyup', this.#onKeyup);\n }\n\n render() {\n return html`\n
\n
\n \n
\n \n
\n
\n `;\n }\n\n updated() {\n this.#closeButton = this.shadowRoot?.getElementById('close-button') ?? null;\n }\n\n async toggle(trapFocus = true) {\n this.open = !this.open;\n await this.updateComplete;\n\n if (trapFocus) {\n if (this.open) {\n this.#closeButton?.focus();\n } else {\n this.#triggerElement?.focus();\n }\n }\n }\n\n #onTriggerClick(event: Event) {\n event.preventDefault();\n this.toggle();\n }\n\n #onClick(event: Event) {\n const path = event.composedPath();\n if (!path.includes(this)) {\n this.toggle();\n }\n }\n\n #onKeydownCloseButton(event: KeyboardEvent) {\n switch (event.key) {\n case 'Enter':\n event.preventDefault();\n this.toggle();\n return;\n }\n }\n\n #onKeydown(event: KeyboardEvent) {\n switch (event.key) {\n case 'Escape': {\n if (!this.open) {\n return;\n }\n this.toggle();\n break;\n }\n default:\n break;\n }\n }\n\n #onKeyup(event: KeyboardEvent) {\n switch (event.key) {\n case 'Tab':\n this.#onTabKeyup(event);\n break;\n default:\n break;\n }\n }\n\n close(trapFocus = true) {\n if (!this.open) {\n return;\n }\n this.toggle(trapFocus);\n }\n\n #onTabKeyup(event: KeyboardEvent) {\n const { target } = event;\n if (target instanceof Node && !this.contains(target)) {\n this.close(false);\n }\n }\n}\n\n@customElement('uxdot-sidenav-item')\nexport class UxdotSideNavItem extends LitElement {\n static styles = [itemStyles];\n\n @property({ type: Boolean, reflect: true }) active = false;\n\n @property() href?: string;\n\n render() {\n const { active } = this;\n return html`\n \n `;\n }\n}\n\n@customElement('uxdot-sidenav-dropdown')\nexport class UxdotSideNavDropdown extends LitElement {\n static styles = [dropdownStyles];\n\n @property({ type: Boolean, reflect: true }) expanded = false;\n\n connectedCallback() {\n super.connectedCallback();\n if (!isServer) {\n this.addEventListener('click', this.#onClick);\n }\n }\n\n render() {\n return html`\n \n `;\n }\n\n async #onClick(event: Event) {\n if (!event.composedPath().some(node => node instanceof HTMLAnchorElement)) {\n event.preventDefault();\n this.expanded = !this.expanded;\n this.querySelector('details')?.toggleAttribute('open', this.expanded);\n // trigger change event which evokes the mutation on this.expanded\n this.dispatchEvent(new CustomEvent('expand', {\n bubbles: true,\n composed: true,\n detail: {\n expanded: this.expanded,\n toggle: this,\n },\n }));\n }\n }\n}\n\n@customElement('uxdot-sidenav-dropdown-menu')\nexport class UxdotSideNavDropdownMenu extends LitElement {\n static styles = [dropdownMenuStyles];\n\n render() {\n return html`\n \n `;\n }\n}\n\n@customElement('uxdot-sidenav-dropdown-menu-item')\nexport class UxdotSideNavDropdownMenuItem extends UxdotSideNavItem {\n static styles = [itemStyles, dropdownMenuItemStyles];\n}\n"]} \ No newline at end of file From 83566438f51650cf26ec63b64d370f09e69c63b7 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 8 Oct 2024 14:53:56 +0300 Subject: [PATCH 04/22] docs: migrate uxdot-pattern usage to ssr --- docs/patterns/card/examples.md | 59 ++++++++++++--------- docs/patterns/card/index.md | 13 +++-- docs/patterns/card/patterns/video.html | 19 ------- docs/patterns/logo-wall/examples.md | 29 +++++----- docs/patterns/tabs/examples.md | 36 +++++++++++++ docs/patterns/tabs/index.md | 38 -------------- docs/patterns/tabs/patterns/code-tabs.html | 61 ++++++++++++++++++++++ docs/patterns/tile/examples.md | 3 +- docs/patterns/tile/index.md | 4 +- docs/theming/color-palettes.css | 13 ----- docs/theming/color-palettes.md | 20 +++---- docs/theming/customizing.md | 7 ++- docs/theming/developers.css | 8 --- docs/theming/developers.md | 8 +-- 14 files changed, 176 insertions(+), 142 deletions(-) delete mode 100644 docs/patterns/card/patterns/video.html create mode 100644 docs/patterns/tabs/patterns/code-tabs.html diff --git a/docs/patterns/card/examples.md b/docs/patterns/card/examples.md index 90dac37352..75d347d001 100644 --- a/docs/patterns/card/examples.md +++ b/docs/patterns/card/examples.md @@ -53,13 +53,18 @@ Use to display that an asset can be downloaded. An icon and label group or text may be used to describe the asset. ### Text and CTA -{% uxdotPattern %}{% include './patterns/asset-text-and-cta.html' %}{% enduxdotPattern %} + + + + ### Title and Link -{% uxdotPattern %}{% include './patterns/asset-title-and-link.html' %}{% enduxdotPattern %} + + ### Title and Link - Top -{% uxdotPattern %}{% include './patterns/asset-title-and-link-top.html' %}{% enduxdotPattern %} + + ## Avatars card @@ -69,55 +74,65 @@ should be included, but including text is optional. Use the [``](/elements/avatar/) element to element to present the list of users. -{% uxdotPattern %}{% include './patterns/avatars.html' %}{% enduxdotPattern %} + + ## Fast facts card Use to display quick facts or short data points under a label. A Secondary call to action may be used or not. -{% uxdotPattern %}{% include './patterns/fast-facts.html' %}{% enduxdotPattern %} + + ## Icon card Use to add an icon to the basic style above the text. Secondary and Default calls to action may be used. -{% uxdotPattern %}{% include './patterns/icon.html' %}{% enduxdotPattern %} + + ## Image card Use to add an image to the basic style above the text. Secondary and Default calls to action may be used. -{% uxdotPattern %}{% include './patterns/image.html' %}{% enduxdotPattern %} + + ## List cards Use to display a short amount of content using various list styles. Secondary and Default calls to action may be used. ### Flat list -{% uxdotPattern %}{% include './patterns/list-flat.html' %}{% enduxdotPattern %} + + ### List with dividers -{% uxdotPattern %}{% include './patterns/list-with-dividers.html' %}{% enduxdotPattern %} + + ### Ordered list -{% uxdotPattern %}{% include './patterns/ordered-list.html' %}{% enduxdotPattern %} + + ### Unordered list -{% uxdotPattern %}{% include './patterns/unordered-list.html' %}{% enduxdotPattern %} + + ## Logo cards Use to display a customer logo in a variety of arrangements. A call to action is required, otherwise use a logo wall. ### CTA only -{% uxdotPattern %}{% include './patterns/logo-cta.html' %}{% enduxdotPattern %} + + ### Text and CTA -{% uxdotPattern %}{% include './patterns/logo-text-and-cta.html' %}{% enduxdotPattern %} + + ## Title bar card @@ -127,7 +142,8 @@ or a logo may be used. Alternative title bar styles can be achieved by selecting [card's `header` CSS Shadow Part](/elements/card/code/#parts). -{% uxdotPattern %}{% include './patterns/title-bar.html' %}{% enduxdotPattern %} + + ## Quote cards @@ -135,19 +151,12 @@ Use to display a short quote with attribution text. Logos, images, and a Secondary call to action may be used or not. ### Basic -{% uxdotPattern %}{% include './patterns/quote.html' %}{% enduxdotPattern %} + + ### Logo and quote -{% uxdotPattern %}{% include './patterns/logo-and-quote.html' %}{% enduxdotPattern %} - - - - + + {% include 'partials/component/feedback.html' %} diff --git a/docs/patterns/card/index.md b/docs/patterns/card/index.md index 2ffbf9eeec..c60a70245b 100644 --- a/docs/patterns/card/index.md +++ b/docs/patterns/card/index.md @@ -25,7 +25,6 @@ subnav: - ## Overview A card formats content in a small, contained space. It can be used to display a @@ -59,22 +58,26 @@ Cards will automatically react to the `color-palette` context provided by a parent element, like ``.
-{% uxdotPattern allow="light, lighter, lightest" %}{% include './patterns/themes.html' %}{% enduxdotPattern %} -{% uxdotPattern allow="dark, darker, darkest" %}{% include './patterns/themes.html' %}{% enduxdotPattern %} + +
### Explicit card theming Cards can play an active role in theming by declaring a specific `color-palette`. -{% uxdotPattern stacked=true %}{% include './patterns/explicit-themes.html' %}{% enduxdotPattern %} + + ### Custom theming When using design tokens to apply custom themes, it is important to ensure that the colors used meet [color contrast guidelines][color-contrast]. -{% uxdotPattern stacked=true %}{% include './patterns/custom-themes.html' %}{% enduxdotPattern %} + + {% include 'partials/component/feedback.html' %} diff --git a/docs/patterns/card/patterns/video.html b/docs/patterns/card/patterns/video.html deleted file mode 100644 index f2b323ebb4..0000000000 --- a/docs/patterns/card/patterns/video.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Call to action - - - Copy to Clipboard - - - - - Copy to Clipboard - - - - \ No newline at end of file diff --git a/docs/patterns/logo-wall/examples.md b/docs/patterns/logo-wall/examples.md index c02d365167..2504d77a6d 100644 --- a/docs/patterns/logo-wall/examples.md +++ b/docs/patterns/logo-wall/examples.md @@ -24,23 +24,28 @@ importElements: ## Within a promo band (bordered) -{% uxdotPattern stacked=true, css='./logo-wall-lightdom.css', target="example-1x1-grid" %} -{% include './patterns/1x1-grid.html' %} -{% enduxdotPattern %} + + ## Within a promo band (borderless) -{% uxdotPattern stacked=true, css='./logo-wall-lightdom.css', target="example-1x1-grid-flat" %} -{% include './patterns/1x1-grid-flat.html' %} -{% enduxdotPattern %} + + + ## With 2x2 grid -{% uxdotPattern stacked=true, css='./logo-wall-lightdom.css', target="example-2x2-grid" %} -{% include './patterns/2x2-grid.html' %} -{% enduxdotPattern %} + + ## With 2x3 grid -{% uxdotPattern stacked=true, css='./logo-wall-lightdom.css', target="example-2x3-grid" %} -{% include './patterns/2x3-grid.html' %} -{% enduxdotPattern %} + + {% include 'partials/component/feedback.html' %} diff --git a/docs/patterns/tabs/examples.md b/docs/patterns/tabs/examples.md index e69de29bb2..53378de70a 100644 --- a/docs/patterns/tabs/examples.md +++ b/docs/patterns/tabs/examples.md @@ -0,0 +1,36 @@ +## Link to tab + +Use this pattern sparingly. If your tabs serve only as + page navigation, use the [Subnav](/elements/subnavigation) element + instead. + +Use to activate a particular tab when the page's URL hash refers to an element +within the tab panel, or to the tab itself. + + + + + + +{% include './patterns/link-to-tab.html' %} + +## Code Tabs + +Use this pattern to display highlighted code blocks of one or more languages, +for example: the HTML, CSS, and JavaScript needed to implement a pattern. + + + + +[element]: /elements/tabs +[css-props]: /elements/tabs/code/#css-custom-properties + diff --git a/docs/patterns/tabs/index.md b/docs/patterns/tabs/index.md index 34975b967c..5f5e7eb0b3 100644 --- a/docs/patterns/tabs/index.md +++ b/docs/patterns/tabs/index.md @@ -13,41 +13,3 @@ tags: import '@rhds/elements/lib/elements/rh-context-picker/rh-context-picker.js'; -{% set linkToTabPattern %}{% include './patterns/link-to-tab.html' %}{% endset %} - -## Link to tab - -Use this pattern sparingly. If your tabs serve only as - page navigation, use the [Subnav](/elements/subnavigation) element - instead. - -Use to activate a particular tab when the page's URL hash refers to an element -within the tab panel, or to the tab itself. - - - Copy to Clipboard - - Toggle word wrap - - - - - - -{% include './patterns/link-to-tab.html' %} - -[element]: /elements/tabs -[css-props]: /elements/tabs/code/#css-custom-properties - diff --git a/docs/patterns/tabs/patterns/code-tabs.html b/docs/patterns/tabs/patterns/code-tabs.html new file mode 100644 index 0000000000..fc456dfa0f --- /dev/null +++ b/docs/patterns/tabs/patterns/code-tabs.html @@ -0,0 +1,61 @@ + + TypeScript + + + + + + CSS + + + + + + + + diff --git a/docs/patterns/tile/examples.md b/docs/patterns/tile/examples.md index 9e51eddc7a..d330d8fee2 100644 --- a/docs/patterns/tile/examples.md +++ b/docs/patterns/tile/examples.md @@ -32,6 +32,7 @@ set of tiles. Be careful not to apply the accented tile pattern to all tiles within a page, otherwise the accent effect will be lost. After all, "if everything is special, then nothing is special." -{% uxdotPattern %}{% include './patterns/accented-tile.html' %}{% enduxdotPattern %} + + View accented tile demo diff --git a/docs/patterns/tile/index.md b/docs/patterns/tile/index.md index a24436419d..c281e54288 100644 --- a/docs/patterns/tile/index.md +++ b/docs/patterns/tile/index.md @@ -53,7 +53,9 @@ Examples include: For more information, please see the docs on [theming][theming] and [`` css custom properties][css-props]. -{% uxdotPattern stacked=true, target="custom-tiles" %}{% include './patterns/custom-themes.html' %}{% enduxdotPattern %} + + [element]: /elements/tile/ [css-props]: /elements/tile/code/#css-custom-properties diff --git a/docs/theming/color-palettes.css b/docs/theming/color-palettes.css index d45c3d7ff6..d198bd4419 100644 --- a/docs/theming/color-palettes.css +++ b/docs/theming/color-palettes.css @@ -2,20 +2,7 @@ width: 100%; } -#elements-grid { - &::part(example) { - display: block; - width: auto; - } -} - .card-snippet-grid { - &::part(example) { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); - gap: var(--rh-space-lg); - } - & h4[slot='heading'] { margin: 0; } diff --git a/docs/theming/color-palettes.md b/docs/theming/color-palettes.md index 0a9904ca1e..4d9ff13fe9 100644 --- a/docs/theming/color-palettes.md +++ b/docs/theming/color-palettes.md @@ -40,9 +40,8 @@ less effort and greater cross-property consistency. RHDS defines six color palettes They range from lightest to darkest, and those are the two palettes you will use the most for the majority of your projects. -{% uxdotPattern id="elements-grid", stacked=true %} -{% include './patterns/collage.html' %} -{% enduxdotPattern %} + + ### How color palettes work @@ -119,9 +118,8 @@ Authors may define the color palette of a container using the `color-palette` HTML attribute. So for example, to create a card with the darkest color palette, use this HTML: -{% uxdotPattern class="card-snippet-grid", fullHeight=true %} -{% include './patterns/card-default-vs-set-palette.html' %} -{% enduxdotPattern %} + + A color palette provider is a **surface** on which a particular color palette is active, as well as a **container** for themeable consumer elements. @@ -164,9 +162,8 @@ Extending our card example from above, if our page author then adds an `` to the card, it will *automatically* adopt the dark color theme. The page author need not and should not customize the CTA. -{% uxdotPattern class="card-snippet-grid", fullHeight=true %} -{% include './patterns/card-child-consumers.html' %} -{% enduxdotPattern %} + + Color palettes lets authors write more HTML, simpler CSS, and @@ -182,9 +179,8 @@ Some elements are both providers and consumers. Card, for example is both a provider and a consumer. It can accept the color theme of its parent context and it can also set its own color palette. -{% uxdotPattern class="card-snippet-grid", fullHeight=true %} -{% include './patterns/card-consumer-provider.html' %} -{% enduxdotPattern %} + + ## Inline color palettes Beta diff --git a/docs/theming/customizing.md b/docs/theming/customizing.md index 95b5c0b004..14d11f6f97 100644 --- a/docs/theming/customizing.md +++ b/docs/theming/customizing.md @@ -40,9 +40,8 @@ To create a custom theme, designers and developers need only set the values for the relevant color properties in the design system. For example, a _Bordeaux_ theme might look like this: -{% uxdotPattern class="card-snippet-grid", stacked=true %} -{% include './patterns/card-bordeaux.html' %} -{% enduxdotPattern %} + + When writing themes, use the semantic, themeable tokens such as `--rh-color-interactive-primary-default-on-light` rather than the crayon tokens @@ -54,7 +53,7 @@ e.g. `--rh-color-purple-10`. Do not set the "computed" theme tokens, e.g. `--rh-color-interactive-primary-default`, those will always be calculated for you from their `-on-light` and `-on-dark` versions. -ke m + diff --git a/docs/theming/developers.css b/docs/theming/developers.css index 799d1c81c7..645f84e150 100644 --- a/docs/theming/developers.css +++ b/docs/theming/developers.css @@ -1,11 +1,3 @@ -.band-example { - &::part(example) { - display: grid; - grid-template-columns: 1fr; - gap: var(--rh-space-lg); - } -} - .code-tabs { border: var(--rh-border-width-sm) solid var(--rh-color-border-subtle); border-radius: var(--rh-border-radius-default); diff --git a/docs/theming/developers.md b/docs/theming/developers.md index 2a23e92a0a..ac1da4e801 100644 --- a/docs/theming/developers.md +++ b/docs/theming/developers.md @@ -56,10 +56,10 @@ A common pattern for a themeable container is the full-width band. For example, a `` may be used as a full-width container and provide the *Bordeaux* theme values to a set of 3 cards in a grid: -{% uxdotPattern src="./docs/theming/patterns/band.html", - class="band-example", - target="band", - stacked=true %}{% enduxdotPattern %} + + ### The `color-palette` attribute From dedd294d000652dfef44b9beb7b7a77f4bf68657 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 8 Oct 2024 16:39:07 +0300 Subject: [PATCH 05/22] docs: fix pattern styles --- .../javascript/elements/uxdot-pattern.css | 19 +++++++++++++++---- .../javascript/elements/uxdot-pattern.ts | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/assets/javascript/elements/uxdot-pattern.css b/docs/assets/javascript/elements/uxdot-pattern.css index 6713b6d6ed..0b236ad2b5 100644 --- a/docs/assets/javascript/elements/uxdot-pattern.css +++ b/docs/assets/javascript/elements/uxdot-pattern.css @@ -1,7 +1,9 @@ @import url('/styles/rh-code-block-lightdom.css'); :host { - container: host inline-size; + container: host / inline-size; + display: block; + margin-block-start: var(--rh-space-2xl); } #container { @@ -11,7 +13,10 @@ gap: var(--rh-space-2xl) var(--rh-space-4xl); @container host (width >= 530px) { - grid-template-areas: 'heading controls' 'content content' 'code code'; + grid-template-areas: + 'heading controls' + 'content content' + 'code code'; } @container host (width >= 992px) { @@ -23,7 +28,13 @@ } } -#heading { grid-area: heading; } +#heading { + grid-area: heading; + display: flex; + align-items: center; +} + +#description { grid-area: content; } h3, ::slotted(h2), @@ -31,7 +42,7 @@ h3, ::slotted(h4), ::slotted(h5), ::slotted(h6) { - margin-block: 0 var(--rh-space-lg) !important; + margin-block: 0 !important; font-size: var(--rh-font-size-body-text-lg) !important; } diff --git a/docs/assets/javascript/elements/uxdot-pattern.ts b/docs/assets/javascript/elements/uxdot-pattern.ts index ac7c411410..3994336330 100644 --- a/docs/assets/javascript/elements/uxdot-pattern.ts +++ b/docs/assets/javascript/elements/uxdot-pattern.ts @@ -79,6 +79,8 @@ export class UxdotPattern extends LitElement { return html`
+

Example

+
@@ -90,7 +92,7 @@ export class UxdotPattern extends LitElement { allow="${this.allow}">
-

Example

+
${this.ssrController.allContent} From 120bd88f702dc94143d32700217b733b66884fa1 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 8 Oct 2024 16:43:25 +0300 Subject: [PATCH 06/22] docs: headings slotted into pattern --- docs/patterns/card/examples.md | 73 ++++++++++++++--------------- docs/patterns/logo-wall/examples.md | 9 ++-- docs/patterns/tabs/examples.md | 24 +++++----- 3 files changed, 51 insertions(+), 55 deletions(-) diff --git a/docs/patterns/card/examples.md b/docs/patterns/card/examples.md index 75d347d001..dca11e5a19 100644 --- a/docs/patterns/card/examples.md +++ b/docs/patterns/card/examples.md @@ -52,97 +52,94 @@ subnav: Use to display that an asset can be downloaded. An icon and label group or text may be used to describe the asset. -### Text and CTA - +

Text and CTA

- -### Title and Link +

Title and Link

-### Title and Link - Top +

Title and Link - Top

-## Avatars card + +

Avatars card

-Use to highlight a group of people who engage in an event. A label -should be included, but including text is optional. + Use to highlight a group of people who engage in an event. A label + should be included, but including text is optional. -Use the [``](/elements/avatar/) element to element to present -the list of users. + Use the [``](/elements/avatar/) element to element to present + the list of users. - -## Fast facts card -Use to display quick facts or short data points under a label. A Secondary -call to action may be used or not. - - +

Fast facts card

-## Icon card + Use to display quick facts or short data points under a label. A Secondary + call to action may be used or not. -Use to add an icon to the basic style above the text. Secondary and Default -calls to action may be used. +
- +

Icon card

-## Image card + Use to add an icon to the basic style above the text. Secondary and Default + calls to action may be used. -Use to add an image to the basic style above the text. -Secondary and Default calls to action may be used. + +

Image card

+ + Use to add an image to the basic style above the text. + Secondary and Default calls to action may be used. +
## List cards Use to display a short amount of content using various list styles. Secondary and Default calls to action may be used. -### Flat list +

Flat list

-### List with dividers +

List with dividers

-### Ordered list - +

Ordered list

-### Unordered list - +

Unordered list

## Logo cards Use to display a customer logo in a variety of arrangements. A call to action is required, otherwise use a logo wall. -### CTA only +

CTA only

### Text and CTA -## Title bar card + +

Title bar card

-Use to add a small icon and a label group to the header section. A larger icon -or a logo may be used. + Use to add a small icon and a label group to the header section. A larger + icon or a logo may be used. -Alternative title bar styles can be achieved by selecting [card's `header` CSS -Shadow Part](/elements/card/code/#parts). + Alternative title bar styles can be achieved by selecting [card's `header` + CSS Shadow Part](/elements/card/code/#parts). - ## Quote cards @@ -150,12 +147,12 @@ Shadow Part](/elements/card/code/#parts). Use to display a short quote with attribution text. Logos, images, and a Secondary call to action may be used or not. -### Basic +

Basic

-### Logo and quote +

Logo and quote

{% include 'partials/component/feedback.html' %} diff --git a/docs/patterns/logo-wall/examples.md b/docs/patterns/logo-wall/examples.md index 2504d77a6d..3be475d274 100644 --- a/docs/patterns/logo-wall/examples.md +++ b/docs/patterns/logo-wall/examples.md @@ -22,30 +22,29 @@ importElements: -## Within a promo band (bordered) - +

Within a promo band (bordered)

-## Within a promo band (borderless) +

Within a promo band (borderless)

-## With 2x2 grid +

With 2x2 grid

-## With 2x3 grid +

With 2x3 grid

{% include 'partials/component/feedback.html' %} diff --git a/docs/patterns/tabs/examples.md b/docs/patterns/tabs/examples.md index 53378de70a..22f4f7464c 100644 --- a/docs/patterns/tabs/examples.md +++ b/docs/patterns/tabs/examples.md @@ -1,13 +1,12 @@ -## Link to tab - -Use this pattern sparingly. If your tabs serve only as - page navigation, use the [Subnav](/elements/subnavigation) element - instead. + +

Link to tab

+ Use this pattern sparingly. If your tabs serve only as + page navigation, use the [Subnav](/elements/subnavigation) element + instead. -Use to activate a particular tab when the page's URL hash refers to an element -within the tab panel, or to the tab itself. + Use to activate a particular tab when the page's URL hash refers to an element + within the tab panel, or to the tab itself. -