diff --git a/.changeset/free-lizards-marry.md b/.changeset/free-lizards-marry.md new file mode 100644 index 0000000000..99a2e6f3b7 --- /dev/null +++ b/.changeset/free-lizards-marry.md @@ -0,0 +1,4 @@ +--- +"@rhds/elements": minor +--- +``: added line numbers diff --git a/.changeset/hot-spiders-prove.md b/.changeset/hot-spiders-prove.md new file mode 100644 index 0000000000..5ff1366f92 --- /dev/null +++ b/.changeset/hot-spiders-prove.md @@ -0,0 +1,11 @@ +--- +"@rhds/elements": minor +--- + +✨ Added `` + +Website status communicates the operational status of a website or domain using a status icon and link. It is usually located in the Footer component. + +```html + +``` \ No newline at end of file diff --git a/.changeset/large-kings-drop.md b/.changeset/large-kings-drop.md new file mode 100644 index 0000000000..740b008108 --- /dev/null +++ b/.changeset/large-kings-drop.md @@ -0,0 +1,6 @@ +--- +"@rhds/elements": patch +--- + +``: improved focus accessibility for keyboard navigation users on firefox +``: improved focus accessibility on firefox diff --git a/.changeset/late-cobras-hammer.md b/.changeset/late-cobras-hammer.md new file mode 100644 index 0000000000..0f0d8c8439 --- /dev/null +++ b/.changeset/late-cobras-hammer.md @@ -0,0 +1,11 @@ +--- +"@rhds/elements": minor +--- + +✨ Added ``. + +Back to top component is a fragment link that allows users to quickly navigate to the top of a lengthy content. + +```html +Back to top +``` \ No newline at end of file diff --git a/.changeset/lemon-comics-flow.md b/.changeset/lemon-comics-flow.md new file mode 100644 index 0000000000..56a3d1a236 --- /dev/null +++ b/.changeset/lemon-comics-flow.md @@ -0,0 +1,5 @@ +--- +"@rhds/elements": patch +--- + +``: added a accents slot with placement options as inline and bottom diff --git a/.changeset/light-rice-warn.md b/.changeset/light-rice-warn.md new file mode 100644 index 0000000000..2b1257a791 --- /dev/null +++ b/.changeset/light-rice-warn.md @@ -0,0 +1,5 @@ +--- +"@rhds/elements": patch +--- + +Context: aligned context implementation with updated [protocol defintions](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md#definitions) diff --git a/.changeset/nervous-hairs-melt.md b/.changeset/nervous-hairs-melt.md new file mode 100644 index 0000000000..1e245439b2 --- /dev/null +++ b/.changeset/nervous-hairs-melt.md @@ -0,0 +1,5 @@ +--- +"@rhds/elements": patch +--- + +Update dependencies, including Lit version 3 diff --git a/.changeset/new-grapes-hope.md b/.changeset/new-grapes-hope.md new file mode 100644 index 0000000000..0cf7a21052 --- /dev/null +++ b/.changeset/new-grapes-hope.md @@ -0,0 +1,5 @@ +--- +"@rhds/elements": patch +--- + +``: make sure alerts always have to correct (lightest) colour palette diff --git a/.changeset/silver-hornets-cry.md b/.changeset/silver-hornets-cry.md new file mode 100644 index 0000000000..b9af1303eb --- /dev/null +++ b/.changeset/silver-hornets-cry.md @@ -0,0 +1,5 @@ +--- +"@rhds/elements": patch +--- + +``: allow tabs with long text content to fit into different-sized containers diff --git a/.changeset/slow-drinks-grow.md b/.changeset/slow-drinks-grow.md new file mode 100644 index 0000000000..2b19cecf86 --- /dev/null +++ b/.changeset/slow-drinks-grow.md @@ -0,0 +1,13 @@ +--- +"@rhds/elements": minor +--- + +✨ Added ``. + +A skip link is used to skip repetitive content on a page. It is hidden by default and can be activated by hitting the "Tab" key after loading/refreshing a page. + +```html + + Skip to main content + +``` diff --git a/.changeset/spicy-cups-fly.md b/.changeset/spicy-cups-fly.md new file mode 100644 index 0000000000..3d7aaaf773 --- /dev/null +++ b/.changeset/spicy-cups-fly.md @@ -0,0 +1,38 @@ +--- +"@rhds/elements": minor +--- +⚛️ Added React wrapper components + +You can now more easily integrate RHDS elements into your React apps by importing our wrapper components + +First, make sure that you list `@lit/react` as a dependency in your project + +```sh +npm install --save @lit/react +``` + +Then import the element components you need and treat them like any other react component + +```js +import { Tabs } from '@rhds/elements/react/rh-tabs/rh-tabs.js'; +import { Tab } from '@rhds/elements/react/rh-tabs/rh-tab.js'; +import { TabPanel } from '@rhds/elements/react/rh-tabs/rh-tab-panel.js'; + +import { useState } from 'react'; + +const tabs = [ + { heading: 'Hello Red Hat', content: 'Let\'s break down silos' }, + { heading: 'Web components', content: 'They work everywhere' } +]; + +function App() { + const [index, setExpanded] = useState(-1); + return ( + expanded {expanded} + {tabs.map(({ heading, content }, i) => ( + setExpanded(i)}>{heading} + {content}))} + + ); +} +``` diff --git a/.changeset/tame-comics-sneeze.md b/.changeset/tame-comics-sneeze.md new file mode 100644 index 0000000000..42b183efc6 --- /dev/null +++ b/.changeset/tame-comics-sneeze.md @@ -0,0 +1,4 @@ +--- +"@rhds/elements": minor +--- +``: added `Show more` toggle diff --git a/.changeset/witty-papayas-tease.md b/.changeset/witty-papayas-tease.md new file mode 100644 index 0000000000..542a7f4e55 --- /dev/null +++ b/.changeset/witty-papayas-tease.md @@ -0,0 +1,16 @@ +--- +"@rhds/elements": minor +--- +``: added copy and wrap actions, with localizable slots for the button labels + +```html + + Copy to Clipboard + + Toggle word wrap + + + +``` diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 34c582799b..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,55 +0,0 @@ -!.eleventy.cjs - -*.mp3 -*.vtt -*.css -*.woff -*.d.ts -*.ico -*.jpeg -*.jpg -*.map -*.md -*.njk -*.patch -*.png -*.scss -*.sh -*.spec.js -*.svg -*.toml -*.tsbuildinfo -*.txt -*.yml -*.yaml -*.min.js -*.tgz -*.csv -CNAME - -custom-elements.json -package-lock.json - -_site -docs/_data/todos.json -docs/demo.js -docs/pfe.min.js -docs/bundle.js -docs/core -docs/components -docs/assets/playgrounds -node_modules - -core/pfe-sass/docs/index.html -core/**/*.js -elements/**/*.js -lib/**/*.js -tools/**/*.js - -!core/*/demo/*.js -!elements/*/demo/*.js - -tools/create-element/templates/**/* -node_modules -node_modules/**/* - diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index cfe32ac8bb..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "@patternfly/elements", - "rules": { - "brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ] - } -} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0002ca1850..0dd76658c1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,8 @@ jobs: - name: Install dependencies run: npm ci --prefer-offline - + - name: Install Playwright Browsers + run: npx playwright install --with-deps - name: Lint id: lint run: npm run lint diff --git a/.gitignore b/.gitignore index 12b4b78b54..1216e5e6df 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ docs/assets/playgrounds/ # Build artifacts elements/*/*.js elements/*/test/*.js +react lib/**/*.js !elements/**/demo/*.css *.map diff --git a/.markdownlint-cli2.mjs b/.markdownlint-cli2.mjs index f0c5b6719a..1e459cf2d5 100644 --- a/.markdownlint-cli2.mjs +++ b/.markdownlint-cli2.mjs @@ -5,23 +5,23 @@ export default { description: 'Require changesets to use the correct package name', tags: ['frontmatter'], function({ name, frontMatterLines, ...rest }, onError) { - const yaml = YAML.load(frontMatterLines.filter(Boolean).filter(x => x !== '---')) + const yaml = YAML.load(frontMatterLines.filter(Boolean).filter(x => x !== '---')); for (const [key, value] of Object.entries(yaml)) { - if (['patch','minor','major'].includes(value)) { + if (['patch', 'minor', 'major'].includes(value)) { if (key !== '@rhds/elements') { onError({ lineNumber: 2, detail: `incorrect package name ${key}`, - }) + }); } } } - } + }, }], dot: true, files: './changeset/*.md', config: { - default: false, + 'default': false, 'markdownlint-changeset-packagename': true, - } -} + }, +}; diff --git a/.nvmrc b/.nvmrc index e44a38e080..790e1105f2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.12.1 +v20.10.0 diff --git a/declaration.d.ts b/declaration.d.ts index ce9a0440cf..8fb49f9bc2 100644 --- a/declaration.d.ts +++ b/declaration.d.ts @@ -1,8 +1,5 @@ declare module '*.css' { - import type { CSSResult } from 'lit'; - - // import style from './some-styles.css'; - const style: CSSResult; + const style: CSSStyleSheet export default style; } diff --git a/docs/_data/playgrounds.cjs b/docs/_data/playgrounds.cjs index 5221bd2b19..b4d7b5c9fc 100644 --- a/docs/_data/playgrounds.cjs +++ b/docs/_data/playgrounds.cjs @@ -8,8 +8,8 @@ function groupBy(prop, xs) { function getDemoFilename(x) { return `demo/${(x.url.split('/demo/').pop() || `${x.primaryElementName}.html`).replace(/\/$/, '.html')}` - .replace('.html', '/index.html') - .replace(`${x.primaryElementName}/index.html`, 'index.html'); + .replace('.html', '/index.html') + .replace(`${x.primaryElementName}/index.html`, 'index.html'); } /** @@ -26,7 +26,8 @@ function getDemoFilename(x) { * > One of `.`, or **WORD** (_>= 1x_) * `"` */ -const DEMO_SUBRESOURCE_RE = /(?href|src)="\/elements\/rh-(?.*)\/(?.*)\.(?[.\w]+)"/g; +const DEMO_SUBRESOURCE_RE = + /(?href|src)="\/elements\/rh-(?.*)\/(?.*)\.(?[.\w]+)"/g; /** * `/elements/` @@ -63,16 +64,16 @@ function demoPaths(content, pathname) { function isModuleScript(node) { return ( - node.tagName === 'script' && - node.attrs.some(x => x.name === 'type' && x.value === 'module') + node.tagName === 'script' + && node.attrs.some(x => x.name === 'type' && x.value === 'module') ); } function isStyleLink(node) { return ( - node.tagName === 'link' && - node.attrs.some(x => x.name === 'rel' && x.value === 'stylesheet') && - node.attrs.some(x => x.name === 'href') + node.tagName === 'link' + && node.attrs.some(x => x.name === 'rel' && x.value === 'stylesheet') + && node.attrs.some(x => x.name === 'href') ); } @@ -130,11 +131,11 @@ module.exports = async function(data) { Tools.createCommentNode('playground-fold'), Tools.createElement('link', { rel: 'stylesheet', - href: 'https://static.redhat.com/libs/redhat/redhat-font/4/webfonts/red-hat-font.min.css' + href: 'https://static.redhat.com/libs/redhat/redhat-font/4/webfonts/red-hat-font.min.css', }), Tools.createElement('link', { rel: 'stylesheet', - href: 'https://static.redhat.com/libs/redhat/redhat-theme/6/advanced-theme.css' + href: 'https://static.redhat.com/libs/redhat/redhat-theme/6/advanced-theme.css', }), Tools.createElement('link', { rel: 'stylesheet', @@ -147,17 +148,19 @@ module.exports = async function(data) { const filename = getDemoFilename(demo); /** @see docs/_plugins/rhds.cjs demoPaths transform */ - const base = url.pathToFileURL(path.join(process.cwd(), 'elements', primaryElementName, 'demo/')); + const base = url.pathToFileURL(path.join(process.cwd(), + 'elements', + primaryElementName, + 'demo/')); const docsDir = url.pathToFileURL(path.join(process.cwd(), 'docs/')); const isMainDemo = filename === 'demo/index.html'; const demoSlug = filename.split('/').at(1); const addSubresourceURL = async subresourceURL => { if (subresourceURL && !subresourceURL.startsWith('http')) { - const subresourceFileURL = !subresourceURL.startsWith('/') + const subresourceFileURL = !subresourceURL.startsWith('/') ? // non-tabular ternary - // eslint-disable-next-line operator-linebreak - ? new URL(subresourceURL, base) + new URL(subresourceURL, base) : new URL(subresourceURL.replace('/', './'), docsDir); try { const resourceName = @@ -171,7 +174,11 @@ module.exports = async function(data) { fileMap.set(resourceName, { content, hidden: true }); } } catch (e) { - throw new SubresourceError(`Error generating playground for ${demo.slug}.\nCould not find subresource ${subresourceURL} at ${subresourceFileURL?.href ?? 'unknown'}`, e, subresourceFileURL); + throw new SubresourceError( + `Error generating playground for ${demo.slug}.\nCould not find subresource ${subresourceURL} at ${subresourceFileURL?.href ?? 'unknown'}`, + e, + subresourceFileURL, + ); } } }; @@ -183,13 +190,13 @@ module.exports = async function(data) { }); const hrefSubresourceElements = Tools.queryAll(fragment, node => - Tools.isElementNode(node) && - isStyleLink(node)); + Tools.isElementNode(node) + && isStyleLink(node)); const srcSubresourceElements = Tools.queryAll(fragment, node => - Tools.isElementNode(node) && - SRC_SUBRESOURCE_TAGNAMES.has(node.tagName) && - hasLocalSrcAttr(node)); + Tools.isElementNode(node) + && SRC_SUBRESOURCE_TAGNAMES.has(node.tagName) + && hasLocalSrcAttr(node)); // register demo css resources for (const el of hrefSubresourceElements) { @@ -219,9 +226,9 @@ module.exports = async function(data) { // HACK: https://github.com/google/playground-elements/issues/93#issuecomment-1775247123 const inlineModules = Tools.queryAll(fragment, node => - Tools.isElementNode(node) && - isModuleScript(node) && - !node.attrs.some(({ name }) => name === 'src')); + Tools.isElementNode(node) + && isModuleScript(node) + && !node.attrs.some(({ name }) => name === 'src')); Array.from(inlineModules).forEach((el, i) => { const moduleName = `${primaryElementName}-${demoSlug.replace('.html', '')}-inline-script-${i++}.js`; diff --git a/docs/_data/relatedItems.yaml b/docs/_data/relatedItems.yaml index 2e6c24df9d..08ae0dc52d 100644 --- a/docs/_data/relatedItems.yaml +++ b/docs/_data/relatedItems.yaml @@ -40,6 +40,7 @@ rh-footer: - rh-accordion - rh-popover - rh-tooltip + - rh-site-status rh-jump-links: - rh-pagination - rh-progress-steps @@ -64,6 +65,13 @@ rh-progress-steps: - rh-jump-links - rh-pagination - rh-tabs +rh-skip-link: + - rh-navigation + - rh-navigation-secondary + - rh-subnav +rh-site-status: + - rh-card + - rh-footer rh-spinner: - form - search-bar diff --git a/docs/_data/repoStatus.yaml b/docs/_data/repoStatus.yaml index 2db363a3c5..baa46d1e5c 100644 --- a/docs/_data/repoStatus.yaml +++ b/docs/_data/repoStatus.yaml @@ -54,6 +54,20 @@ status: Ready - name: Documentation status: Ready +- name: "Back To Top" + type: "Element" + overallStatus: "Available" + libraries: + - name: Figma library + status: Ready + - name: Responsive + status: Ready + - name: RH Elements + status: Ready + - name: webRH + status: Planned + - name: Documentation + status: In Progress - name: "Badge" type: "Element" overallStatus: "Available" @@ -222,6 +236,34 @@ status: Ready - name: Documentation status: Ready +- name: "Skip Link" + type: "Element" + overallStatus: "New" + libraries: + - name: Figma library + status: Ready + - name: Responsive + status: Ready + - name: RH Elements + status: Ready + - name: webRH + status: Planned + - name: Documentation + status: Ready +- name: "Site Status" + type: "Element" + overallStatus: "New" + libraries: + - name: Figma library + status: Ready + - name: Responsive + status: Ready + - name: RH Elements + status: Ready + - name: webRH + status: Planned + - name: Documentation + status: Ready - name: "Spinner" type: "Element" overallStatus: "Available" diff --git a/docs/_includes/component/header.njk b/docs/_includes/component/header.njk index 058ab9a7d4..e2ebda2107 100755 --- a/docs/_includes/component/header.njk +++ b/docs/_includes/component/header.njk @@ -48,7 +48,7 @@ - +
  • @@ -61,7 +61,7 @@ {%- for link in collections.getstarted -%}
  • {{ link.data.title }} + href="{{ link.url | url }}">{{ link.data.heading }}
  • {%- endfor -%} diff --git a/docs/_includes/layout-foundations.njk b/docs/_includes/layout-with-subnav.njk similarity index 64% rename from docs/_includes/layout-foundations.njk rename to docs/_includes/layout-with-subnav.njk index 553658ae12..81f792b66d 100644 --- a/docs/_includes/layout-foundations.njk +++ b/docs/_includes/layout-with-subnav.njk @@ -7,6 +7,7 @@ importElements: - rh-subnav - rh-tag - rh-badge + - rh-code-block --- {% include 'component/header.njk' %} @@ -14,13 +15,16 @@ importElements:
    -
    +
    diff --git a/docs/_plugins/alphabetize-tags.cjs b/docs/_plugins/alphabetize-tags.cjs index 8c2eb9ebf0..87f0ac089e 100644 --- a/docs/_plugins/alphabetize-tags.cjs +++ b/docs/_plugins/alphabetize-tags.cjs @@ -3,7 +3,7 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { for (const tag of tagsToAlphabetize) { eleventyConfig.addCollection(tag, function(collection) { const currentCollection = [...collection.getFilteredByTag(tag)] - .sort((a, b) => (a.data.order ?? Infinity) - (b.data.order ?? Infinity)); + .sort((a, b) => (a.data.order ?? Infinity) - (b.data.order ?? Infinity)); // Final sorted array of collection items const sorted = new Set(); diff --git a/docs/_plugins/cem-shortcodes.cjs b/docs/_plugins/cem-shortcodes.cjs new file mode 100644 index 0000000000..c68483dc59 --- /dev/null +++ b/docs/_plugins/cem-shortcodes.cjs @@ -0,0 +1,456 @@ +/** quick and dirty dedent, also provides in-editor syntax highlighting */ +const html = (...args) => + String.raw(...args) + .split('\n') + .map(x => x.replace(/^ {6}/, '')) + .join('\n'); + +/** @typedef {import('@patternfly/pfe-tools/11ty/DocsPage').DocsPage} DocsPage */ +module.exports = function(eleventyConfig) { + eleventyConfig.addPairedShortcode('renderCodeDocs', + function renderCodeDocs(content, kwargs = {}) { + const renderers = new Renderers(this, kwargs); + return renderers.renderAll(content); + } + ); +}; + +function innerMD(content = '') { + const trimmed = content.trim(); + return trimmed && `\n\n\n${trimmed}\n\n\n`; +} + +function mdHeading(content, length = 2) { + const hashes = Array.from({ length }, () => '#').join(''); + return innerMD(`${hashes} ${content}`); +} + +function type(content = '', { lang = 'ts' } = {}) { + return content.trim() && `\n\n\`\`\`${lang}\n${content.trim()}\n\n\`\`\`\n\n`; +} + +function stringifyParams(method) { + return method.parameters?.map?.(p => + `${p.name}: ${p.type?.text ?? 'unknown'}`).join(', ') ?? ''; +} + +function renderBand(content, { level, header = '' } = {}) { + return html` +
    + ${header && mdHeading(header, { level })} + ${innerMD(content)} +
    `; +} + +/** + * docs pages contain a #styling-hooks anchor as back compat for older versions of the page + * to prevent this id from rendering more than once, we track the number of times each page + * renders css custom properties. + */ +const cssStylingHookIdTracker = new WeakSet(); + +/** @param {import('@11ty/eleventy').UserConfig} eleventyConfig */ +module.exports = function(eleventyConfig) { + eleventyConfig.addFilter('innerMD', innerMD); + eleventyConfig.addFilter('mdHeading', mdHeading); + eleventyConfig.addFilter('type', type); + eleventyConfig.addFilter('stringifyParams', stringifyParams); + eleventyConfig.addPairedShortcode('band', renderBand); + for (const shortCode of [ + 'renderAttributes', + 'renderCssCustomProperties', + 'renderCssParts', + 'renderEvents', + 'renderMethods', + 'renderOverview', + 'renderProperties', + 'renderInstallation', + 'renderSlots', + ]) { + eleventyConfig.addPairedShortcode(shortCode, function(content, kwargs) { + return Renderers.forPage(this)[shortCode](content, kwargs); + }); + } +}; + +class Renderers { + /** @type{WeakMap} */ + static renderers = new WeakMap(); + static forPage(page) { + return new Renderers(page); + } + + constructor(page) { + if (Renderers.renderers.has(page)) { + return Renderers.renderers.get(page); + } + /** + * NB: since the data for this shortcode is no a POJO, + * but a DocsPage instance, 11ty assigns it to this.ctx._ + * @see https://github.com/11ty/eleventy/blob/bf7c0c0cce1b2cb01561f57fdd33db001df4cb7e/src/Plugins/RenderPlugin.js#L89-L93 + * @type {DocsPage} + */ + this.docsPage = page.ctx._; + this.manifest = this.docsPage.manifest; + Renderers.renderers.set(page, this); + } + + packageTagName(kwargs) { + if (kwargs.for && !kwargs.for.match(/@/)) { + return kwargs.for; + } else { + const [, tagName = this.tagName] = (kwargs?.for ?? '').match(/@[-\w]+\/(.*)/) ?? []; + return tagName ?? this.docsPage.tagName; + } + } + + /** + * Render the overview of a component page + * @param {string} content - shortcode content + */ + renderOverview(content) { + return html` +
    +

    Overview

    +
    + ${content} +
    +
    + +
    +

    Installation

    + + ~~~shell + npm install ${this.manifest.packageJson.name} + ~~~ + +
    `; + } + + /** + * Render the list of element attributes + * @param {string} content section content + * @param {object} kwargs shortcode keyword args + * @param {string} [kwargs.header] heading text + * @param {number} [kwargs.level] heading level (e.g. `3` for `

    `) + */ + renderAttributes(content, { header = 'Attributes', level = 2, ...kwargs } = {}) { + const _attrs = this.manifest.getAttributes(this.packageTagName(kwargs)) ?? []; + const deprecated = _attrs.filter(x => x.deprecated); + const attributes = _attrs.filter(x => !x.deprecated); + return html` +
    + ${mdHeading(header)}${!content && !attributes.length ? html` + None` : html` + ${innerMD(content)} +
    ${attributes?.map(attribute => html` +
    ${attribute.name}
    +
    + ${innerMD(attribute.description)} +
    ${!attribute.fieldName ? '' : html` +
    DOM Property
    +
    ${attribute.fieldName}
    `} +
    Type
    +
    ${type(attribute.type?.text ?? 'unknown')}
    +
    Default
    +
    ${type(attribute.default ?? 'unknown')}
    +
    +
    `).join('\n') ?? ''} +
    `}${!deprecated.length ? '' : html` +
    + ${mdHeading(`Deprecated ${header}`, { level: level + 1 })} +
    ${deprecated.map(attribute => html` +
    ${attribute.name}
    +
    + ${innerMD(attribute.description)} + Note: ${attribute.name} is deprecated. ${innerMD(attribute.deprecated)} +
    ${!attribute.fieldName ? '' : html` +
    DOM Property
    +
    ${attribute.fieldName}
    `} +
    Type
    +
    ${innerMD(attribute.type?.text ?? 'unknown')}
    +
    Default
    +
    ${innerMD(attribute.default ?? 'unknown')}
    +
    +
    `).join('\n')} +
    +
    `} +
    `; + } + + /** Render the list of element DOM properties */ + renderProperties(content, { header = 'DOM Properties', level = 2, ...kwargs } = {}) { + const allProperties = this.manifest.getProperties(this.packageTagName(kwargs)) ?? []; + const deprecated = allProperties.filter(x => x.deprecated); + const properties = allProperties.filter(x => !x.deprecated); + // TODO: inline code highlighting for type and default: render the markdown to html and extract the `` from the `
    `
    +    return html`
    +      
    + ${mdHeading(header)}${!content && !properties.length ? html` + None` : html` + ${innerMD(content)} +
    ${properties.map(property => html` +
    ${property.name}
    +
    + ${innerMD(property.description)} +
    +
    Type
    +
    ${type(property.type?.text ?? 'unknown')}
    +
    Default
    +
    ${type(property.default ?? 'unknown')}
    +
    +
    `).join('\n')} +
    `}${!deprecated.length ? '' : html` +
    + ${mdHeading(`Deprecated ${header}`, { level: level + 1 })} +
    ${deprecated.map(property => html` +
    ${property.name}
    +
    + ${innerMD(property.description)} + Note: ${property.name} is deprecated. ${innerMD(property.deprecated)} +
    +
    Type
    +
    ${type(property.type?.text ?? 'unknown')}
    +
    Default
    +
    ${type(property.default ?? 'unknown')}
    +
    +
    `).join('\n')} +
    +
    `} +
    `; + } + + /** + * Render a table of element CSS Custom Properties + * @param {string} content shortcode content + * @param {object} [kwargs] shortcode keyword args + * @param {string} [kwargs.header] heading text + * @param {number} [kwargs.level] heading level (e.g. `3` for `

    `) + */ + renderCssCustomProperties(content, { + header = 'CSS Custom Properties', + level = 2, + ...kwargs + } = {}) { + const allCssProperties = + this.manifest.getCssCustomProperties(this.packageTagName(kwargs)) ?? []; + const cssProperties = allCssProperties.filter(x => !x.deprecated); + const deprecated = allCssProperties.filter(x => x.deprecated); + return html` +
    + ${mdHeading(header)}${!content && !cssProperties.length ? html` + None` : html` + ${innerMD(content)} + + + + + + + + + ${cssProperties.map(prop => html` + + + + + `).join('\n')} + +
    CSS PropertyDescriptionDefault
    ${prop.name}${innerMD(prop.description ?? '')}${!prop.default?.startsWith('#') ? html` + ` : html` + `} + ${prop.default ?? '—'} + +
    `}${!deprecated.length ? '' : html` +
    + ${mdHeading(`Deprecated ${header}`, { level: level + 1 })} + + + + + + + + + ${deprecated.map(prop => html` + + + + + `).join('\n')} + +
    CSS PropertyDescriptionDefault
    ${prop.name}${innerMD(prop.description)}${innerMD(prop.default ?? '—')}
    +
    `} +
    `; + } + + /** Render the list of element CSS Shadow Parts */ + renderCssParts(content, { header = 'CSS Shadow Parts', level = 2, ...kwargs } = {}) { + const allParts = this.manifest.getCssParts(this.packageTagName(kwargs)) ?? []; + const parts = allParts.filter(x => !x.deprecated); + const deprecated = allParts.filter(x => x.deprecated); + return html` +
    + ${mdHeading(header)}${!content && !parts.length ? html` + None` : html` + ${innerMD(content)} +
    ${parts.map(part => html` +
    ${part.name}
    +
    ${innerMD(part.description)}
    `).join('\n')} +
    `}${!deprecated.length ? '' : html` +
    + ${mdHeading(`Deprecated ${header}`, { level: level + 1 })} +
    ${deprecated.map(part => html` +
    ${part.name}
    +
    + ${innerMD(part.description)} + Note: ${part.name} is deprecated. ${innerMD(part.deprecated)} +
    `).join('\n')} +
    +
    `} +
    `; + } + + /** Render the list of events for the element */ + renderEvents(content, { header = 'Events', level = 2, ...kwargs } = {}) { + const _events = this.manifest.getEvents(this.packageTagName(kwargs)) ?? []; + const deprecated = _events.filter(x => x.deprecated); + const events = _events.filter(x => !x.deprecated); + return html` +
    + ${mdHeading(header)}${!content && !events.length ? html` + None` : html` + ${innerMD(content)} +
    ${events.map(event => html` +
    ${event.name}
    +
    + ${innerMD(event.description)} + + Event Type: ${type(event.type?.text ?? 'unknown')} + +
    `).join('\n')} +
    `}${!deprecated.length ? '' : html` +
    + ${mdHeading(`Deprecated ${header}`, { level: level + 1 })} +
    ${deprecated.map(event => html` +
    ${event.name}
    +
    + ${innerMD(event.description)} + Note: ${event.name} is deprecated. ${innerMD(event.deprecated)} + Event Type: ${type(event.type?.text ?? 'unknown')} +
    `).join('\n')} +
    +
    `} +
    `; + } + + /** + * Render the installation instructions for the element + * @param {string} content shortcode content + * @param {object} [kwargs] shortcode keyword args + * @param {string} [kwargs.header] heading text + * @param {number} [kwargs.level] heading level (e.g. `3` for `

    `) + * @param {string} [kwargs.tagName] tag name to print instructions for + */ + renderInstallation(content, { + header = 'Installation', + level = 2, + tagName = this.docsPage.tagName, + } = {}) { + return html` +
    + ${header} + + We recommend loading elements via a CDN such as [JSPM][inst-jspm] and + using an import map to manage your dependencies. + + For more information on import maps and how to use them, + see the [import map reference on MDN Web Docs][inst-mdn]. + + If you are using node and NPM, you can install this component using npm: + + ~~~shell + npm install ${this.manifest.packageJson.name} + ~~~ + + Then import this component into your project by using a + [bare module specifier][inst-bms]: + + ~~~js + import '@rhds/elements/${tagName}/${tagName}.js'; + ~~~ + + **Please Note** You should either load elements via a CDN or + install them locally through NPM. *Do not do both.* + + ${content} + +
    + + [inst-jspm]: https://jspm.dev + [inst-mdn]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap/ + [inst-bms]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules`; + } + + /** Render the list of element methods */ + renderMethods(content, { header = 'Methods', level = 2, ...kwargs } = {}) { + const allMethods = this.manifest.getMethods(this.packageTagName(kwargs)) ?? []; + const deprecated = allMethods.filter(x => x.deprecated); + const methods = allMethods.filter(x => !x.deprecated); + // TODO: inline code highlighting for type and default: render the markdown to html and extract the `` from the `
    `
    +    return html`
    +      
    + ${mdHeading(header)}${!content && !methods.length ? html` + None` : html` + ${innerMD(content)} +
    ${methods.map(method => html` +
    ${method.name}(${stringifyParams(method)})
    +
    ${innerMD(method.description)}
    `).join('\n')} +
    `}${!deprecated.length ? '' : html` +
    + ${mdHeading(`Deprecated ${header}`, { level: level + 1 })} +
    ${deprecated.map(method => html` +
    ${method.name}(${stringifyParams(method)})
    +
    + ${innerMD(method.description)} + Note: ${method.name} is deprecated. ${innerMD(method.deprecated)} +
    `).join('\n')} +
    +
    `} +
    `; + } + + /** Render the list of the element's slots */ + renderSlots(content, { header = 'Slots', level = 2, ...kwargs } = {}) { + const allSlots = this.docsPage.manifest.getSlots(this.packageTagName(kwargs)) ?? []; + const slots = allSlots.filter(x => !x.deprecated); + const deprecated = allSlots.filter(x => x.deprecated); + return html` +
    + ${mdHeading(header)}${!content && !slots.length ? html` + None` : html` + ${innerMD(content)} +
    ${slots.map(slot => html` +
    ${slot.name ? html` + ${slot.name}` : html` + Default Slot`} +
    +
    ${innerMD(slot.description)}
    `).join('\n')} +
    `}${!deprecated.length ? '' : html` +
    + ${mdHeading(`Deprecated ${header}`, { level: level + 1 })} +
    ${deprecated.map(slot => html` +
    ${slot.name ? html` + ${slot.name}` : html` + Default Slot`} +
    +
    + ${innerMD(slot.description)} + Note: ${slot.name} is deprecated. ${innerMD(slot.deprecated)} +
    `).join('\n')} +
    +
    `} +
    `; + } +} diff --git a/docs/_plugins/importMap.cjs b/docs/_plugins/importMap.cjs index 9f27c2aa38..b465d63a82 100644 --- a/docs/_plugins/importMap.cjs +++ b/docs/_plugins/importMap.cjs @@ -8,7 +8,11 @@ function logPerf() { /* eslint-disable no-console */ const chalk = require('chalk'); const TOTAL = performance.measure('importMap-total', 'importMap-start', 'importMap-end'); - const RESOLVE = performance.measure('importMap-resolve', 'importMap-start', 'importMap-afterLocalPackages'); + const RESOLVE = performance.measure( + 'importMap-resolve', + 'importMap-start', + 'importMap-afterLocalPackages' + ); if (TOTAL.duration > 2000) { console.log( `🦥 Import map generator done in ${chalk.red(TOTAL.duration)}ms\n`, @@ -61,7 +65,9 @@ async function getCachedImportMap({ const providers = { '@patternfly': 'nodemodules', ...Object.fromEntries(localPackages?.map(packageName => - packageName.match(/@(rhds|patternfly)/) ? [nothing] : [packageName, 'nodemodules']) ?? []), + packageName.match(/@(rhds|patternfly)/) ? + [nothing] + : [packageName, 'nodemodules']) ?? []), }; delete providers[nothing]; @@ -132,7 +138,7 @@ module.exports = function(eleventyConfig, { inputMap, localPackages, cwd, - assetCache + assetCache, }); }); diff --git a/docs/_plugins/markdown-it.cjs b/docs/_plugins/markdown-it.cjs index e3239e4a12..9d8bf6550a 100644 --- a/docs/_plugins/markdown-it.cjs +++ b/docs/_plugins/markdown-it.cjs @@ -22,21 +22,21 @@ const rhdsPermalink = makePermalink((slug, opts, anchorOpts, state, idx) => { content: /* html */` <${headerOpen.tag} ${headerOpen.attrs.map(([key, value]) => `${key}="${value}"`).join(' ')}> - `.trim() + `.trim(), }), - inline, - Object.assign(new state.Token('html_block', '', 0), { content: /* html */` + inline, + Object.assign(new state.Token('html_block', '', 0), { content: /* html */` `.trim(), - }) + }) ); }); /** @param {import('@11ty/eleventy/src/UserConfig')} eleventyConfig */ module.exports = function(eleventyConfig) { eleventyConfig.amendLibrary('md', /** @param {import('markdown-it')} md*/md => md - .set({ html: true, breaks: false }) - .use(markdownItAnchor, { permalink: rhdsPermalink() }) - .use(markdownItAttrs)); + .set({ html: true, breaks: false }) + .use(markdownItAnchor, { permalink: rhdsPermalink() }) + .use(markdownItAttrs)); }; diff --git a/docs/_plugins/rhds.cjs b/docs/_plugins/rhds.cjs index e069af11a9..29932eefd2 100644 --- a/docs/_plugins/rhds.cjs +++ b/docs/_plugins/rhds.cjs @@ -28,7 +28,9 @@ function demoPaths(content) { const el = $(this); const attr = el.attr('href') ? 'href' : 'src'; const val = el.attr(attr); - if (!val) { return; } + if (!val) { + return; + } if (!val.startsWith('http') && !val.startsWith('/') && !val.startsWith('#')) { el.attr(attr, `${isNested ? '../' : ''}${val}`); } else if (val.startsWith('/elements/rh-')) { @@ -50,7 +52,7 @@ const LIGHTDOM_PATH_RE = /href="\.(.*)"/; function prettyDate(dateStr, options = {}) { const { dateStyle = 'medium' } = options; return new Intl.DateTimeFormat('en-US', { dateStyle }) - .format(new Date(dateStr)); + .format(new Date(dateStr)); } function getTagNameSlug(tagName, config) { @@ -127,7 +129,7 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { const filesToCopy = getFilesToCopy(); eleventyConfig.addPassthroughCopy(filesToCopy, { - filter: /** @param {string} path */path => !path.endsWith('.html'), + filter: /** @param {string} path pathname */path => !path.endsWith('.html'), }); eleventyConfig.addTransform('demo-subresources', demoPaths); @@ -143,7 +145,10 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { const { tagName } = tagNameMatch.groups; // slugify the value of each key in aliases creating a new cloned copy - const modifiedAliases = Object.fromEntries(Object.entries(aliases).map(([key, value]) => [slugify(key, { strict: true, lower: true }), value])); + const modifiedAliases = Object.fromEntries(Object.entries(aliases).map(([key, value]) => [ + slugify(key, { strict: true, lower: true }), + value, + ])); // does the tagName exist in the aliases object? const key = Object.keys(modifiedAliases).find(key => modifiedAliases[key] === tagName); @@ -156,8 +161,8 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { const [, path] = match.match(LIGHTDOM_PATH_RE) ?? []; const { pathname } = new URL(path, `file:///${outputPath}`); content = content.replace(`.${path}`, pathname - .replace(`/_site/elements/${redirect.old}/`, `/assets/packages/@rhds/elements/elements/${redirect.new}/`) - .replace('/demo/', '/')); + .replace(`/_site/elements/${redirect.old}/`, `/assets/packages/@rhds/elements/elements/${redirect.new}/`) + .replace('/demo/', '/')); } } } @@ -166,10 +171,10 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { }); eleventyConfig.addFilter('getTitleFromDocs', function(docs) { - return docs.find(x => x.docsPage?.title)?.alias ?? - docs[0]?.alias ?? - docs[0]?.docsPage?.title ?? - eleventyConfig.getFilter('deslugify')(docs[0]?.slug); + return docs.find(x => x.docsPage?.title)?.alias + ?? docs[0]?.alias + ?? docs[0]?.docsPage?.title + ?? eleventyConfig.getFilter('deslugify')(docs[0]?.slug); }); /** get the element overview from the manifest */ @@ -202,7 +207,7 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { return { name: x, url: slug === x ? `/patterns/${slug}` : `/elements/${slug}`, - text: pfeconfig.aliases[x] || deslugify(slug) + text: pfeconfig.aliases[x] || deslugify(slug), }; }).sort((a, b) => a.text < b.text ? -1 : a.text > b.text ? 1 : 0); return related; @@ -215,7 +220,26 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { eleventyConfig.addCollection('sortedColor', async function(collectionApi) { const colorCollection = collectionApi.getFilteredByTags('color'); return colorCollection.sort((a, b) => { - if (a.data.order > b.data.order) { return 1; } else if (a.data.order < b.data.order) { return -1; } else { return 0; } + if (a.data.order > b.data.order) { + return 1; + } else if (a.data.order < b.data.order) { + return -1; + } else { + return 0; + } + }); + }); + + eleventyConfig.addCollection('sortedDevelopers', async function(collectionApi) { + const developersCollection = collectionApi.getFilteredByTags('developers'); + return developersCollection.sort((a, b) => { + if (a.data.order > b.data.order) { + return 1; + } else if (a.data.order < b.data.order) { + return -1; + } else { + return 0; + } }); }); @@ -254,7 +278,7 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { screenshotPath, permalink, href, - overviewHref + overviewHref, }; } @@ -262,20 +286,22 @@ module.exports = function(eleventyConfig, { tagsToAlphabetize }) { /** @type {(import('@patternfly/pfe-tools/11ty/DocsPage').DocsPage & { repoStatus?: any[] })[]} */ const elements = await eleventyConfig.globalData?.elements(); const filePaths = (await glob(`elements/*/docs/*.md`, { cwd: process.cwd() })) - .filter(x => x.match(/\d{1,3}-[\w-]+\.md$/)); // only include new style docs + .filter(x => x.match(/\d{1,3}-[\w-]+\.md$/)); // only include new style docs const { repoStatus } = collectionApi.items.find(item => item.data?.repoStatus)?.data || []; return filePaths - .map(filePath => { - const props = getProps(filePath); - const docsPage = elements.find(x => x.tagName === props.tagName); - if (docsPage) { docsPage.repoStatus = repoStatus; } - const tabs = filePaths - .filter(x => x.split('/docs/').at(0) === (`elements/${props.tagName}`)) - .sort() - .map(x => getProps(x)); - return { docsPage, tabs, ...props }; - }) - .sort(alphabeticallyBySlug); + .map(filePath => { + const props = getProps(filePath); + const docsPage = elements.find(x => x.tagName === props.tagName); + if (docsPage) { + docsPage.repoStatus = repoStatus; + } + const tabs = filePaths + .filter(x => x.split('/docs/').at(0) === (`elements/${props.tagName}`)) + .sort() + .map(x => getProps(x)); + return { docsPage, tabs, ...props }; + }) + .sort(alphabeticallyBySlug); } catch (e) { // it's important to surface this // eslint-disable-next-line no-console diff --git a/docs/_plugins/shortcodes.cjs b/docs/_plugins/shortcodes.cjs index dd3f8c6768..3c26b30bde 100644 --- a/docs/_plugins/shortcodes.cjs +++ b/docs/_plugins/shortcodes.cjs @@ -1,5 +1,9 @@ const Playground = require('./shortcodes/playground.cjs'); -const { RepoStatusList, RepoStatusChecklist, RepoStatusTable } = require('./shortcodes/repoStatus.cjs'); +const { + RepoStatusList, + RepoStatusChecklist, + RepoStatusTable, +} = require('./shortcodes/repoStatus.cjs'); const RenderInstallation = require('./shortcodes/renderInstallation.cjs'); const ExampleImage = require('./shortcodes/example.cjs'); const Cta = require('./shortcodes/cta.cjs'); diff --git a/docs/_plugins/shortcodes/alert.cjs b/docs/_plugins/shortcodes/alert.cjs index 45ea8758cd..4438abb735 100644 --- a/docs/_plugins/shortcodes/alert.cjs +++ b/docs/_plugins/shortcodes/alert.cjs @@ -7,13 +7,13 @@ module.exports = function(eleventyConfig) { * @param {string} content * @param {Record} attrs */ - function alert(content, { - state = 'info', - title = 'Note:', - style = null, - level = 3, - } = {}) { - return /* html */` + function alert(content, { + state = 'info', + title = 'Note:', + style = null, + level = 3, + } = {}) { + return /* html */` ${title} @@ -23,5 +23,5 @@ module.exports = function(eleventyConfig) { `; - }); + }); }; diff --git a/docs/_plugins/shortcodes/cta.cjs b/docs/_plugins/shortcodes/cta.cjs index 40cdeb1600..a5156ad6aa 100644 --- a/docs/_plugins/shortcodes/cta.cjs +++ b/docs/_plugins/shortcodes/cta.cjs @@ -1,11 +1,14 @@ const { attrMap } = require('./helpers.cjs'); module.exports = function(eleventyConfig) { - eleventyConfig.addPairedShortcode('cta', + eleventyConfig.addPairedShortcode( + 'cta', /** * Render a Call to Action - * @param {string} content - * @param {Record} attrs + * @param {string} content shortcode content + * @param {object} [attrs] cta link attrs + * @param {object} [attrs.href] cta link href + * @param {object} [attrs.target] optional cta link target */ async function cta(content, { href = '#', diff --git a/docs/_plugins/shortcodes/demo.cjs b/docs/_plugins/shortcodes/demo.cjs index 8ecb8fb009..e657f9e0eb 100644 --- a/docs/_plugins/shortcodes/demo.cjs +++ b/docs/_plugins/shortcodes/demo.cjs @@ -9,13 +9,13 @@ module.exports = function(eleventyConfig) { * @param {string} options.palette Palette to apply, e.g. lightest, light see components/_section.scss * @param {string} options.headingLevel The heading level, defaults to 3 */ - function demoShortcode(content, { - headline = null, - palette = 'light', - headingLevel = '3', - } = {}) { - const slugify = eleventyConfig.getFilter('slugify'); - return /* html*/` + function demoShortcode(content, { + headline = null, + palette = 'light', + headingLevel = '3', + } = {}) { + const slugify = eleventyConfig.getFilter('slugify'); + return /* html*/`
    ${!headline ? '' : ` ${headline}`} @@ -33,5 +33,5 @@ ${content.trim()}
    `; - }); + }); }; diff --git a/docs/_plugins/shortcodes/example.cjs b/docs/_plugins/shortcodes/example.cjs index 3c61e4b728..45996268f6 100644 --- a/docs/_plugins/shortcodes/example.cjs +++ b/docs/_plugins/shortcodes/example.cjs @@ -1,24 +1,27 @@ // @ts-check const { attrMap } = require('./helpers.cjs'); - -/** @typedef {import('../shortcodes.cjs').EleventyContext} EleventyContext */ - const { promisify } = require('node:util'); -const Image = require('@11ty/eleventy-img'); -const sizeOf = promisify(/** @type{import('image-size').default}*/(/** @type{unknown}*/(require('image-size') ))); +const EleventyImage = require('@11ty/eleventy-img'); +const sizeOf = promisify( + /** @type{import('image-size').default}*/( + /** @type{unknown}*/( + require('image-size') + ) + ) +); const path = require('path'); /** * generate images and return metadata - * @param {Image.ImageSource} url + * @param {EleventyImage.ImageSource} url * @param {'auto' | number | null} width1x * @param {'auto' | number | null} width2x * @param {string} outputDir * @param {string} urlPath */ async function getImg(url, width1x, width2x, outputDir, urlPath) { - return await Image(url, { + return await EleventyImage(url, { urlPath, outputDir, formats: ['auto'], @@ -50,7 +53,7 @@ async function getImageHTML(opts) { const styles = [`width:${width1x}px`, `height:auto`].join(';'); const img = await getImg(srcHref, width1x, width2x, outputDir, urlPath); const sizes = `(max-width: ${width1x}px) ${width1x}px, ${width2x}px`; - return `${!img ? '' : Image.generateHTML(img, { alt, sizes, style: styles, loading, decoding })}`; + return `${!img ? '' : EleventyImage.generateHTML(img, { alt, sizes, style: styles, loading, decoding })}`; } else { return `${alt}`; } @@ -58,11 +61,11 @@ async function getImageHTML(opts) { /** @param {import('@11ty/eleventy/src/UserConfig')} eleventyConfig */ module.exports = function(eleventyConfig) { - eleventyConfig.addShortcode('example', + eleventyConfig.addShortcode( + 'example', /** * Example * An example image or component - * * @param {object} options * @param {string} [options.alt] Image alt text * @param {string} [options.src] Image url @@ -70,10 +73,9 @@ module.exports = function(eleventyConfig) { * @param {string} [options.style] styles for the wrapper * @param {string} [options.wrapperClass] class names for container element * @param {string} [options.headline] Text to go in the heading - * @param {string} [options.palette='light'] Palette to apply, e.g. lightest, light see components/_section.scss - * @param {2|3|4|5|6} [options.headingLevel=3] The heading level - * @param {boolean} [options.srcAbsolute=false] If true, doesn't include the page url in the img src - * @this {EleventyContext} + * @param {string} [options.palette] Palette to apply, e.g. lightest, light see components/_section.scss + * @param {2|3|4|5|6} [options.headingLevel] The heading level + * @param {boolean} [options.srcAbsolute] If true, doesn't include the page url in the img src */ async function example({ alt = '', @@ -95,7 +97,17 @@ module.exports = function(eleventyConfig) { const loading = 'lazy'; const decoding = 'async'; const classes = `example example--palette-${palette} ${wrapperClass ?? ''}`; - const imageHTML = src && await getImageHTML({ alt, decoding, imageFile, loading, outputDir, src, srcHref, style, urlPath }); + const imageHTML = src && await getImageHTML({ + alt, + decoding, + imageFile, + loading, + outputDir, + src, + srcHref, + style, + urlPath, + }); return /* html */`
    ${!headline ? '' : ` diff --git a/docs/_plugins/shortcodes/feedback.cjs b/docs/_plugins/shortcodes/feedback.cjs index 490bfc2807..421f6fc28c 100644 --- a/docs/_plugins/shortcodes/feedback.cjs +++ b/docs/_plugins/shortcodes/feedback.cjs @@ -11,8 +11,8 @@ module.exports = function(eleventyConfig) { * @param {string} options.palette Palette to apply, e.g. lightest, light see components/_section.scss * @param {string} options.headingLevel The heading level, defaults to 3 */ - function demoShortcode(content) { - return /* html*/` + function demoShortcode(content) { + return /* html*/` `; - }); + }); }; diff --git a/docs/_plugins/shortcodes/helpers.cjs b/docs/_plugins/shortcodes/helpers.cjs index 521c691a33..ee50db4eae 100644 --- a/docs/_plugins/shortcodes/helpers.cjs +++ b/docs/_plugins/shortcodes/helpers.cjs @@ -16,7 +16,7 @@ function getAttrMapValue(k, v) { */ exports.attrMap = function attrMap(attrObj) { return Object.entries(attrObj) - .filter(([, v]) => v != null) - .map(([k, v]) => `${k}="${getAttrMapValue(k, v)}"`) - .join(' '); + .filter(([, v]) => v != null) + .map(([k, v]) => `${k}="${getAttrMapValue(k, v)}"`) + .join(' '); }; diff --git a/docs/_plugins/shortcodes/playground.cjs b/docs/_plugins/shortcodes/playground.cjs index 4f7de7a88f..748415cb3d 100644 --- a/docs/_plugins/shortcodes/playground.cjs +++ b/docs/_plugins/shortcodes/playground.cjs @@ -23,8 +23,8 @@ async function playground(_, { const options = getPfeConfig(); const { filePath } = docsPage.manifest - .getDemoMetadata(tagName, options) - ?.find(x => x.url === `https://ux.redhat.com/elements/${x.slug}/demo/`) ?? {}; + .getDemoMetadata(tagName, options) + ?.find(x => x.url === `https://ux.redhat.com/elements/${x.slug}/demo/`) ?? {}; const content = filePath && await readFile(filePath, 'utf8'); return /* html*/` diff --git a/docs/_plugins/shortcodes/renderCodeDocs.cjs b/docs/_plugins/shortcodes/renderCodeDocs.cjs index 4a3d859ea8..6b318d0d2a 100644 --- a/docs/_plugins/shortcodes/renderCodeDocs.cjs +++ b/docs/_plugins/shortcodes/renderCodeDocs.cjs @@ -5,17 +5,17 @@ const { copyCell, getTokenHref } = require('../tokensHelpers.cjs'); /** quick and dirty dedent, also provides in-editor syntax highlighting */ const html = (...args) => String.raw(...args) - .split('\n') - .map(x => x.replace(/^ {6}/, '')) - .join('\n'); + .split('\n') + .map(x => x.replace(/^ {6}/, '')) + .join('\n'); /** @typedef {import('@patternfly/pfe-tools/11ty/DocsPage').DocsPage} DocsPage */ module.exports = function(eleventyConfig) { eleventyConfig.addPairedShortcode('renderCodeDocs', - function renderCodeDocs(content, kwargs = {}) { - const renderers = new Renderers(this, kwargs); - return renderers.renderAll(content); - } + function renderCodeDocs(content, kwargs = {}) { + const renderers = new Renderers(this, kwargs); + return renderers.renderAll(content); + } ); }; @@ -214,8 +214,12 @@ class Renderers { } /** Render a table of element Design Tokens */ - renderTokens(content, { header = 'Design Tokens', level = 2, ...kwargs } = {}) { - const allCssProperties = this.manifest.getCssCustomProperties(this.packageTagName(kwargs)) ?? []; + renderTokens(content, { + header = 'Design Tokens', + ...kwargs + } = {}) { + const allCssProperties = + this.manifest.getCssCustomProperties(this.packageTagName(kwargs)) ?? []; const elTokens = allCssProperties.filter(x => tokens.has(x.name)); return html`
    @@ -242,8 +246,12 @@ class Renderers { } /** Render a table of element CSS Custom Properties */ - renderCssCustomProperties(content, { header = 'CSS Custom Properties', level = 2, ...kwargs } = {}) { - const allCssProperties = this.manifest.getCssCustomProperties(this.packageTagName(kwargs)) ?? []; + renderCssCustomProperties(content, { + header = 'CSS Custom Properties', + level = 2, ...kwargs + } = {}) { + const allCssProperties = + this.manifest.getCssCustomProperties(this.packageTagName(kwargs)) ?? []; const cssProperties = allCssProperties.filter(x => !x.deprecated && !tokens.has(x.name)); const deprecated = allCssProperties.filter(x => x.deprecated && !tokens.has(x.name)); return html` diff --git a/docs/_plugins/shortcodes/repoStatus.cjs b/docs/_plugins/shortcodes/repoStatus.cjs index e163f90f09..90695662ae 100644 --- a/docs/_plugins/shortcodes/repoStatus.cjs +++ b/docs/_plugins/shortcodes/repoStatus.cjs @@ -17,9 +17,9 @@ const STATUS_LEGEND = { s1.5-0.7,1.5-1.5S8.8,1,8,1z"/> - ` + `, }, - 'In progress': { + 'In Progress': { description: 'In the design or development process', color: 'green', variant: 'outline', @@ -30,7 +30,7 @@ const STATUS_LEGEND = { -` +`, }, 'Ready': { description: 'Ready to use and approved by all team members', @@ -40,7 +40,7 @@ const STATUS_LEGEND = { -` +`, }, 'Deprecated': { description: 'No longer supported by RHDS', @@ -51,7 +51,7 @@ const STATUS_LEGEND = { -` +`, }, 'N/A': { description: 'Not planned, not available, or does not apply', @@ -64,7 +64,7 @@ const STATUS_LEGEND = { -` +`, }, 'Beta': { color: 'purple', @@ -78,7 +78,7 @@ const STATUS_LEGEND = { - ` + `, }, 'Experimental': { color: 'orange', @@ -110,7 +110,7 @@ const STATUS_LEGEND = { c0.659-0.5036,1.31714-1.07642,1.95532-1.7146c0.63739-0.63739,1.20898-1.29462,1.7121-1.9527 C13.97107,11.15002,14.26593,13.04779,13.65686,13.65686z"/> -` +`, }, 'New': { color: 'cyan', @@ -125,7 +125,7 @@ const STATUS_LEGEND = { - ` + `, }, }; @@ -136,46 +136,46 @@ const STATUS_CHECKLIST = { 'In progress': 'Component will be added to the Figma library when ready', 'Planned': 'Component is scheduled to be worked on', 'Deprecated': 'Component has been removed from the current Figma library ', - 'N/A': 'Component not available in the Figma library' + 'N/A': 'Component not available in the Figma library', }, 'Responsive': { 'Ready': 'Component responds to changing viewport sizes in Figma and the browser', - 'N/A': 'Responsiveness does not apply to this component' + 'N/A': 'Responsiveness does not apply to this component', }, 'RH Elements': { 'Ready': 'Component is available as a web component', 'In progress': 'Component will be added to the RH Elements repo when ready', 'Planned': 'Component is scheduled to become a web component', 'Deprecated': 'Component is no longer a web component', - 'N/A': 'Component not available as a web component' + 'N/A': 'Component not available as a web component', }, 'webRH': { 'Ready': 'Component is available as a web component', 'In progress': 'Component will be added to the webRH repo when ready', 'Planned': 'Component is scheduled to become a web component', 'Deprecated': 'Component is no longer a web component', - 'N/A': 'Component not available as a web component' - } + 'N/A': 'Component not available as a web component', + }, }; /** * Reads repo status data from global data and outputs an array with component keys - * @this {EleventyContext} */ function getRepoData() { const docsPage = this.ctx._; const allStatuses = this.ctx.repoStatus ?? docsPage?.repoStatus ?? []; const title = this.ctx.title ?? docsPage?.title; - return allStatuses.find(component => component.name === title && component.type === 'Element').libraries; + return allStatuses.find( + component => component.name === title && component.type === 'Element' + )?.libraries; } /** * Calls getRepoData function and outputs a definition list for each component - * @this {EleventyContext} */ function repoStatusList({ heading = 'Status', level = 2 } = {}) { // Removing Documentation status from the repoStatusList - const statusList = getRepoData.call(this).filter(repo => repo.name !== 'Documentation'); + const statusList = getRepoData.call(this)?.filter(repo => repo.name !== 'Documentation'); if (!Array.isArray(statusList) || !statusList.length) { return ''; @@ -212,13 +212,15 @@ ${listItem.status}${STATUS_LEGEND[listItem.status].icon} /** * Reads component status data from global data (see above) and outputs a table for Design/Code status page - * @this {EleventyContext} */ function repoStatusTable() { const docsPage = this.ctx._; const allStatuses = this.ctx.repoStatus ?? docsPage?.repoStatus ?? []; // Filtering out 'Responsive' status from all the libraries - const elementsList = allStatuses.map(item => ({ ...item, libraries: item.libraries.filter(lib => lib.name !== 'Responsive') })); + const elementsList = allStatuses.map(item => ({ + ...item, + libraries: item.libraries.filter(lib => lib.name !== 'Responsive'), + })); if (!Array.isArray(elementsList) || !elementsList.length) { return ''; @@ -275,11 +277,10 @@ ${listItem.libraries.map(lib => { /** * Calls getRepoData function and outputs a status checklist table for each component - * @this {EleventyContext} */ function repoStatusChecklist({ heading = 'Status checklist', level = 2 } = {}) { const headingLevel = Array.from({ length: level }, () => '#').join(''); - const statusList = getRepoData.call(this).filter(repo => repo.name !== 'Documentation'); + const statusList = getRepoData.call(this)?.filter(repo => repo.name !== 'Documentation'); if (!Array.isArray(statusList) || !statusList.length) { return ''; } else { diff --git a/docs/_plugins/shortcodes/rh-playground.js b/docs/_plugins/shortcodes/rh-playground.js index 6688ee49f1..0b41a74977 100644 --- a/docs/_plugins/shortcodes/rh-playground.js +++ b/docs/_plugins/shortcodes/rh-playground.js @@ -77,8 +77,12 @@ class RhPlayground extends LitElement { const { config } = await import(`/assets/playgrounds/${this.tagName}-playground.js`); this.config = config; this.demos = Object.entries(config.files ?? {}) - .filter(([, { contentType }]) => contentType?.startsWith('text/html')) - .map(([filename, { label }]) => ({ filename, label, active: filename === 'demo/index.html' })); + .filter(([, { contentType }]) => contentType?.startsWith('text/html')) + .map(([filename, { label }]) => ({ + filename, + label, + active: filename === 'demo/index.html', + })); await import('playground-elements'); this.requestUpdate(); this.show(); diff --git a/docs/_plugins/shortcodes/spacerTokensTable.cjs b/docs/_plugins/shortcodes/spacerTokensTable.cjs index 9e78ed6c37..cab4cab9b9 100644 --- a/docs/_plugins/shortcodes/spacerTokensTable.cjs +++ b/docs/_plugins/shortcodes/spacerTokensTable.cjs @@ -2,11 +2,11 @@ const { tokens: metaTokens } = require('@rhds/tokens/meta.js'); /** * Reads token data from @rhds/tokens and outputs a table for specified tokens - * @this {EleventyContext} + * @param {import('@11ty/eleventy').UserConfig} eleventyConfig computed config */ - module.exports = function(eleventyConfig) { - eleventyConfig.addPairedShortcode('spacerTokensTable', + eleventyConfig.addPairedShortcode( + 'spacerTokensTable', function(content, { tokens = '', style, @@ -14,11 +14,11 @@ module.exports = function(eleventyConfig) { headingLevel = '3', caption = '', wrapperClass, - palette = 'light' + palette = 'light', } = {}) { const slugify = eleventyConfig.getFilter('slugify'); const tokenList = (Array.isArray(tokens) ? tokens : tokens.split(',')) - .map(token => token.trim()).filter(Boolean); + .map(token => token.trim()).filter(Boolean); const metaData = []; if (tokenList.length === 0) { diff --git a/docs/_plugins/shortcodes/tokensTable.cjs b/docs/_plugins/shortcodes/tokensTable.cjs index a157d32a39..a8e22436ec 100644 --- a/docs/_plugins/shortcodes/tokensTable.cjs +++ b/docs/_plugins/shortcodes/tokensTable.cjs @@ -4,8 +4,8 @@ module.exports = function(eleventyConfig) { * Tokens Table * Display a table rows with token usage */ - function(content) { - const show = process.env.SHOW_TOKENS_TABLE; - return show !== 'false' ? `${content}` : ``; - }); + function(content) { + const show = process.env.SHOW_TOKENS_TABLE; + return show !== 'false' ? `${content}` : ``; + }); }; diff --git a/docs/_plugins/tokens.cjs b/docs/_plugins/tokens.cjs index 33b89e22c1..bf7e6f1e15 100644 --- a/docs/_plugins/tokens.cjs +++ b/docs/_plugins/tokens.cjs @@ -14,7 +14,7 @@ const { /** * Generate an HTML table of tokens - * @param {object} [opts={}] + * @param {object} [opts] * @param {object} opts.tokens the collection of tokens to render * @param {string} opts.name the name of the collection * @param {object} opts.docs the docs extension for the collection @@ -35,33 +35,33 @@ function table({ tokens, name = '', docs, options } = {}) { - ${tokens.map(token => { /* eslint-disable indent */ - const { r, g, b } = token.attributes?.rgb ?? {}; - const { h, s, l } = token.attributes?.hsl ?? {}; - const isColor = !!token.path.includes('color'); - const isCrayon = isColor && token.name.match(/0$/); - const isDimension = token.$type === 'dimension'; - const isHSLorRGB = isColor && !!token.name.match(/(hsl|rgb)$/); - const isFamily = !!token.path.includes('family'); - const isFont = !!token.path.includes('font'); - const isRadius = !!token.path.includes('radius'); - const isSize = !!token.path.includes('size'); - const isWeight = !!token.path.includes('weight'); - const isWidth = !!token.path.includes('width'); - - return isHSLorRGB ? '' : /* html */` + ${tokens.map(token => { + const { r, g, b } = token.attributes?.rgb ?? {}; + const { h, s, l } = token.attributes?.hsl ?? {}; + const isColor = !!token.path.includes('color'); + const isCrayon = isColor && token.name.match(/0$/); + const isDimension = token.$type === 'dimension'; + const isHSLorRGB = isColor && !!token.name.match(/(hsl|rgb)$/); + const isFamily = !!token.path.includes('family'); + const isFont = !!token.path.includes('font'); + const isRadius = !!token.path.includes('radius'); + const isSize = !!token.path.includes('size'); + const isWeight = !!token.path.includes('weight'); + const isWidth = !!token.path.includes('width'); + + return isHSLorRGB ? '' : /* html */` + '--radius': isRadius ? token.$value : 'initial', + '--width': isWidth ? token.$value : 'initial', + '--color': isColor ? token.$value : 'initial', + '--font-family': isFamily ? token.$value : 'var(--rh-font-family-body-text)', + '--font-size': isSize ? token.$value : 'var(--rh-font-size-heading-md)', + '--font-weight': isWeight ? token.$value : 'var(--rh-font-weight-body-text-regular)', + [`--${token.attributes.type === 'icon' && token.$type === 'dimension' ? `${name}-size` : name}`]: token.$value, + })}"> ${isColor && token.path.includes('text') ? 'Aa' @@ -122,23 +122,25 @@ function table({ tokens, name = '', docs, options } = {}) { `}`; - }).map(dedent).join('\n')} + }).map(dedent).join('\n')} `).trim(); - /* eslint-enable indent */ } /** Returns Markdown from the Tokens source YAML files OR from linked markdown files */ function getTokenDocs(path) { - const { parent, key } = getParentCollection({ path }, require('@rhds/tokens/json/rhds.tokens.json')); + const { parent, key } = + getParentCollection({ path }, require('@rhds/tokens/json/rhds.tokens.json')); const collection = parent[key]; return getDocs(collection, { docsExtension: 'com.redhat.ux' }); } /** * @param {import('@11ty/eleventy/src/UserConfig')} eleventyConfig - * @param {PluginOptions} [pluginOptions={}] + * @param {PluginOptions} [pluginOptions] */ -module.exports = function RHDSPlugin(eleventyConfig, pluginOptions = { }) { +module.exports = function RHDSPlugin( + eleventyConfig, + pluginOptions = { }) { eleventyConfig.addGlobalData('tokens', tokensJSON); eleventyConfig.addGlobalData('tokenCategories', require('./tokenCategories.json')); @@ -160,7 +162,8 @@ module.exports = function RHDSPlugin(eleventyConfig, pluginOptions = { }) { [join(__dirname, '11ty', '*')]: assetsPath, }); - eleventyConfig.addShortcode('category', + eleventyConfig.addShortcode( + 'category', async function category(options = {}) { options.tokens ??= await eleventyConfig.globalData.tokens; options.attrs ??= pluginOptions.attrs ?? (() => ''); @@ -170,10 +173,15 @@ module.exports = function RHDSPlugin(eleventyConfig, pluginOptions = { }) { const path = options.path ?? '.'; const level = options.level ?? 2; const exclude = options.exclude ?? []; - const include = Array.isArray(options.include) ? options.include : [options.include].filter(Boolean); + const include = Array.isArray(options.include) ? + options.include + : [options.include].filter(Boolean); const name = options.name ?? path.split('.').pop(); - const { parent, key } = getParentCollection(options, eleventyConfig.globalData.tokens ?? eleventyConfig.globalData?.tokenCategories); + const { parent, key } = getParentCollection( + options, + eleventyConfig.globalData.tokens ?? eleventyConfig.globalData?.tokenCategories + ); const collection = parent[key]; const docs = getDocs(collection, options); const heading = docs?.heading ?? capitalize(name.replace('-', ' ')); @@ -185,17 +193,20 @@ module.exports = function RHDSPlugin(eleventyConfig, pluginOptions = { }) { * @example isChildEntry(['500', tokens.color.blue.500]); // false */ const isChildEntry = ([key, value]) => - !value.$value && typeof value === 'object' && !key.startsWith('$') && !exclude.includes(key); + !value.$value + && typeof value === 'object' + && !key.startsWith('$') + && !exclude.includes(key); const children = Object.entries(collection) - .filter(isChildEntry) - .map(([key], i, a) => ({ - path: key, - parent: collection, - level: level + 1, - parentName: `${parentName} ${name}`.trim(), - isLast: i === a.length - 1, - })); + .filter(isChildEntry) + .map(([key], i, a) => ({ + path: key, + parent: collection, + level: level + 1, + parentName: `${parentName} ${name}`.trim(), + isLast: i === a.length - 1, + })); /** * 0. render the description @@ -210,18 +221,18 @@ module.exports = function RHDSPlugin(eleventyConfig, pluginOptions = { }) { ${(dedent(await getDescription(collection, pluginOptions)))}
    - ${await table({ /* eslint-disable indent */ - tokens: Object.values(collection).filter(x => x.$value), - options, - name, - docs, - })/* eslint-enable indent */} + ${await table({ + tokens: Object.values(collection).filter(x => x.$value), + options, + name, + docs, + })} ${(await Promise.all(children.map(category))).join('\n')} - ${(await Promise.all(include.map((path, i, a) => category({ /* eslint-disable indent */ - path, - level: level + 1, - isLast: !a[i + 1], - })))).join('\n')/* eslint-enable indent*/} + ${(await Promise.all(include.map((path, i, a) => category({ + path, + level: level + 1, + isLast: !a[i + 1], + })))).join('\n')} `); }); }; diff --git a/docs/_plugins/tokensHelpers.cjs b/docs/_plugins/tokensHelpers.cjs index 9a0511eeb9..d5826bea11 100644 --- a/docs/_plugins/tokensHelpers.cjs +++ b/docs/_plugins/tokensHelpers.cjs @@ -53,7 +53,7 @@ function getDescription(collection, options) { const { filePath = getFilePathGuess(collection), description = '', - descriptionFile + descriptionFile, } = getDocs(collection, options) ?? {}; if (description) { diff --git a/docs/assets/copy-permalink.js b/docs/assets/copy-permalink.js index 4d8ed8b818..93d9570310 100644 --- a/docs/assets/copy-permalink.js +++ b/docs/assets/copy-permalink.js @@ -47,10 +47,13 @@ customElements.define('copy-permalink', class CopyPermalink extends HTMLElement constructor() { super(); this.attachShadow({ mode: 'open' }) - .append(hPermalinkTpl.content.cloneNode(true)); + .append(hPermalinkTpl.content.cloneNode(true)); this.shadowRoot.adoptedStyleSheets = [hPermalinkStyle]; const button = this.shadowRoot.getElementById('button'); - button.setAttribute('aria-label', this.getAttribute('copy-button-label') ?? 'Copy link to clipboard'); + button.setAttribute( + 'aria-label', + this.getAttribute('copy-button-label') ?? 'Copy link to clipboard' + ); button.addEventListener('click', async () => { const { href } = this.querySelector('a'); if (href) { diff --git a/docs/assets/elements/uxdot-copy-button.js b/docs/assets/elements/uxdot-copy-button.js index 9b637e8956..af05462561 100644 --- a/docs/assets/elements/uxdot-copy-button.js +++ b/docs/assets/elements/uxdot-copy-button.js @@ -51,5 +51,7 @@ export class UxdotCopyButton extends LitElement { toast({ heading: 'Copied', message }); } - static { customElements.define(this.is, this); } + static { + customElements.define(this.is, this); + } } diff --git a/docs/assets/elements/uxdot-installation-tabs.js b/docs/assets/elements/uxdot-installation-tabs.js index b6ea3e4690..52ee908c1b 100644 --- a/docs/assets/elements/uxdot-installation-tabs.js +++ b/docs/assets/elements/uxdot-installation-tabs.js @@ -12,7 +12,9 @@ export class InstallationTabs extends RhTabs { static is = 'uxdot-installation-tabs'; - static { customElements.define(this.is, this); } + static { + customElements.define(this.is, this); + } #onExpand(event) { // TODO: when tabs is decoupled from PFE, update the event type here @@ -35,9 +37,9 @@ export class InstallationTabs extends RhTabs { )); if (InstallationTabs.stored !== null) { const index = parseInt(InstallationTabs.stored); - if (!Number.isNaN(index) && - index !== this.activeIndex && - this.panels[index]) { + if (!Number.isNaN(index) + && index !== this.activeIndex + && this.panels[index]) { this.activeIndex = index; } } @@ -55,5 +57,7 @@ export class InstallationTabPanel extends RhTabPanel { } `]; - static { customElements.define(this.is, this); } + static { + customElements.define(this.is, this); + } } diff --git a/docs/assets/elements/uxdot-search.js b/docs/assets/elements/uxdot-search.js index 441164bc6e..65032f7c19 100644 --- a/docs/assets/elements/uxdot-search.js +++ b/docs/assets/elements/uxdot-search.js @@ -96,14 +96,31 @@ class UxdotSearch extends LitElement { #internals = this.attachInternals(); - get form() { return this.#internals.form; } + #ariaLabel = ''; - get value() { return this.#input.value; } - set value(value) { this.#input.value = value ?? ''; } + get form() { + return this.#internals.form; + } + + get value() { + return this.#input.value; + } + + set value(value) { + this.#input.value = value ?? ''; + } - get #input() { return this.shadowRoot.getElementById('input'); } - get #firstLink() { return this.shadowRoot.querySelector('li a'); } - get #lastLink() { return this.shadowRoot.querySelector('li:last-of-type a'); } + get #input() { + return this.shadowRoot.getElementById('input'); + } + + get #firstLink() { + return this.shadowRoot.querySelector('li a'); + } + + get #lastLink() { + return this.shadowRoot.querySelector('li:last-of-type a'); + } get selectedItem() { return this.shadowRoot.querySelector('[aria-selected="true"]'); @@ -116,12 +133,14 @@ class UxdotSearch extends LitElement { this.addEventListener('blur', this.#onBlur); } - firstUpdated() { - if (this.hasAttribute('aria-label')) { - this.#input.setAttribute('aria-label', this.getAttribute('aria-label')); - this.setAttribute('original-aria-label', this.getAttribute('aria-label')); - this.removeAttribute('aria-label'); + connectedCallback() { + super.connectedCallback(); + this.#ariaLabel = this.getAttribute('aria-label') || undefined; + this.removeAttribute('aria-label'); + if (this.#ariaLabel) { + this.setAttribute('original-aria-label', this.#ariaLabel); } + this.requestUpdate(); } render() { @@ -129,12 +148,13 @@ class UxdotSearch extends LitElement {
    -
      +
        ${(this.items ?? []).map((item, i) => !item ? '' : html`
      1. - label.includes(cta.dataset.category)) ?? []; + label.includes(cta.dataset.category)); cta.variant = categoryHasTokenResults ? 'secondary' : 'brick'; } diff --git a/docs/assets/tokens/tokens.js b/docs/assets/tokens/tokens.js index ea268a8daa..1391bd1aee 100644 --- a/docs/assets/tokens/tokens.js +++ b/docs/assets/tokens/tokens.js @@ -8,11 +8,11 @@ import '@rhds/elements/rh-footer/rh-footer-universal.js'; import '/assets/elements/uxdot-search.js'; document - .getElementById('search-input') - .addEventListener('focus', async function() { - const { init } = await import('/assets/search-tokens.js'); - init(document.getElementById('search-tokens')); - }, { once: true }); + .getElementById('search-input') + .addEventListener('focus', async function() { + const { init } = await import('/assets/search-tokens.js'); + init(document.getElementById('search-tokens')); + }, { once: true }); // colour variants for (const details of document.querySelectorAll('.variants details')) { diff --git a/docs/foundations/color/accessibility.md b/docs/foundations/color/accessibility.md index ad1dd89f86..0254f64758 100644 --- a/docs/foundations/color/accessibility.md +++ b/docs/foundations/color/accessibility.md @@ -1,10 +1,11 @@ --- -layout: layout-foundations.njk +layout: layout-with-subnav.njk title: Accessibility heading: Color tags: - color permalink: /foundations/color/accessibility/index.html +subNavCollection: sortedColor order: 20 bodyClasses: element-docs --- diff --git a/docs/foundations/color/index.md b/docs/foundations/color/index.md index be16e80aab..786ce2515c 100644 --- a/docs/foundations/color/index.md +++ b/docs/foundations/color/index.md @@ -1,11 +1,12 @@ --- -layout: layout-foundations.njk +layout: layout-with-subnav.njk title: Overview heading: Color tags: - foundations - color permalink: /foundations/color/index.html +subNavCollection: sortedColor order: 00 bodyClasses: element-docs --- diff --git a/docs/foundations/color/usage.md b/docs/foundations/color/usage.md index 7735d2b969..4a3dd66f29 100644 --- a/docs/foundations/color/usage.md +++ b/docs/foundations/color/usage.md @@ -1,10 +1,11 @@ --- -layout: layout-foundations.njk +layout: layout-with-subnav.njk title: Usage heading: Color tags: - color permalink: /foundations/color/usage/index.html +subNavCollection: sortedColor order: 10 bodyClasses: element-docs --- diff --git a/docs/get-started.md b/docs/get-started.md index 36ebc2c841..9eb0f0f817 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -37,13 +37,15 @@ The Red Hat Design System for digital experiences gives designers and developers
        Designers
        -
        - {% example - palette="descriptive", - alt="Card overlapping code editor user interface", - src="/assets/get-started/developers.png" %} -
        Developers (Coming soon)
        -
        + +
        + {% example + palette="descriptive", + alt="Card overlapping code editor user interface", + src="/assets/get-started/developers.png" %} +
        Developers
        +
        +
        {% feedback %} diff --git a/docs/get-started/designers.md b/docs/get-started/designers.md index 3d3bad2cf8..5fa0e3fc87 100644 --- a/docs/get-started/designers.md +++ b/docs/get-started/designers.md @@ -1,6 +1,7 @@ --- layout: layout-basic.njk title: Designers +heading: Designers order: 2 tags: - getstarted diff --git a/docs/get-started/developers/index.md b/docs/get-started/developers/index.md new file mode 100644 index 0000000000..462f43a14b --- /dev/null +++ b/docs/get-started/developers/index.md @@ -0,0 +1,52 @@ +--- +layout: layout-with-subnav.njk +title: Overview +heading: Developers +tags: + - getstarted + - developers +permalink: /get-started/developers/index.html +subNavCollection: sortedDevelopers +order: 00 +bodyClasses: element-docs +--- + +## Introduction + +Welcome to the **Red Hat Design System** (RHDS) for digital experiences. If you need to develop something using our design system, you have come to the right place. + +Read this section to get started and e-mail [design-system@redhat.com](mailto:design-system@redhat.com) or connect with us on Slack if you have any questions along the way. + +## Learn about our design system + +Our design system libraries and the documentation website offer assets and guidance needed to create digital experiences. Please use these resources to have a better understanding of how to use our design system. + +
        +
        +

        Foundations

        +

        Foundations are how we express our brand through color, space, typography, etc.

        +
        +
        +

        Design tokens

        +

        Design tokens are how we translate our design language decisions into code.

        +
        +
        +

        Documentation

        +

        This website offers guidance about how to use our elements and patterns correctly.

        +
        +
        +

        GitHub repositories

        +

        Explore our code, roadmaps, and discussions in the Red Hat Design System repo and the Red Hat Design Tokens repo.

        +
        +
        + +## About web components + +Web components are based on a set of web platform APIs that help to create reusable and encapsulated UI elements. Those standards consist of custom elements, shadow DOM, and HTML Templates. + +Because they're able to work with any framework that supports HTML, web components can be used without having to rework all of your code and are less likely to be affected by changes in preferred web stacks. Encapsulation within the shadow DOM prevents a page's code from breaking a component's style and allows for a scalable design system. + +{% feedback %} +

        Designers

        +

        To get started using our design system as a designer, go to the Designers page.

        +{% endfeedback %} \ No newline at end of file diff --git a/docs/get-started/developers/installation.md b/docs/get-started/developers/installation.md new file mode 100644 index 0000000000..e4d6db4240 --- /dev/null +++ b/docs/get-started/developers/installation.md @@ -0,0 +1,107 @@ +--- +layout: layout-with-subnav.njk +title: Installation +heading: Developers +tags: + - developers +permalink: /get-started/developers/installation/index.html +subNavCollection: sortedDevelopers +order: 10 +bodyClasses: element-docs +--- + +## How to install + +There are three ways you can install the Red Hat Design System's web components: CDN, NPM, or JSPM. Each element's "Code" page includes the same installation information with code snippets that are specific to that element. + +### Red Hat CDN + +{% alert title="CDN Prerelease", + state="warning" %} +

        We are currently working on our CDN, which will be soon moving into beta. This will be the preferred method of installation in the near future. If you are a Red Hat associate and have questions or comments about the CDN or installation process please connect with us on Slack.

        +{% endalert %} + +The recommended way to load RHDS is via the Red Hat Digital Experience CDN, and using an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap). + +If you have full control over the page you are using, add an import map to the ``, pointing to the CDN, or update any existing import map. If you are not responsible for the page's ``, request that the page owner makes the change on your behalf. + + + + + +Once the import map is established, you can load the element with the following module, containing a [bare module specifier](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). The example below shows how you'd load in <`rh-button>`. + + + + + +Note that modules may be placed in the ``. Since they are deferred by default, they will not block rendering. + +### NPM + +Install RHDS using your team's preferred NPM package manager. + + + + + +Once that's been accomplished, you will need to use a bundler to resolve the bare module specifiers and optionally optimize the package for your site's particular use case and needs. Comprehensive guides to bundling are beyond the scope of this page; read more about bundlers on their websites: + +- [Rollup](https://rollupjs.org/) +- [esbuild](https://esbuild.github.io/) +- [Parcel](https://parceljs.org/) +- [Webpack](https://webpack.js.org/) + +### JSPM + +{% alert title="Public CDNs", + state="warning" %} +

        JSPM and other public CDNs should not be used on corporate domains. Use them for development purposes only!

        +{% endalert %} + +Add an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) to the ``, pointing to the CDN, or update any existing import map. + + + + + +Once the import map is established, you can load the element with the following module, containing a [bare module specifier](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). The example below shows how you'd load in <`rh-button>`. + + + + + +Note that Modules may be placed in the ``. Since they are deferred by default, they will not block rendering. + +{% feedback %} +

        Designers

        +

        To get started using our design system as a designer, go to the Designers page.

        +{% endfeedback %} \ No newline at end of file diff --git a/docs/get-started/developers/tokens.md b/docs/get-started/developers/tokens.md new file mode 100644 index 0000000000..9b0a9970c2 --- /dev/null +++ b/docs/get-started/developers/tokens.md @@ -0,0 +1,165 @@ +--- +layout: layout-with-subnav.njk +title: Tokens +heading: Developers +tags: + - developers +permalink: /get-started/developers/tokens/index.html +subNavCollection: sortedDevelopers +order: 30 +bodyClasses: element-docs +--- + +## How to install tokens + +Run the following git command to install RHDS tokens: + + + + + +## Usage + +We use [style-dictionary](https://amzn.github.io/style-dictionary/) to transform our tokens into multiple formats and helpers. + +### Import global CSS + +Apply defaults to the document root by importing the global stylesheet: + + + + + +### Reset the shadowroot + +Reset a component's styles (preventing inheritance) by adding resetStyles to its static Constructible Style Sheet list: + + + + + + + +### Import tokens as JavaScript objects + +{% alert title="Note", state="info" %} +We strongly recommend using CSS variables (and accompanying snippets), instead of importing tokens as JavaScript objects. +{% endalert %} + +Import tokens as JavaScript objects: + + + + + + + +or for tree-shakable imports: + + + + + + + +## Plugins + +### Using editor snippets + +Editor snippets complete prefixes like `--rh-color-brand` to their CSS custom properties, complete with fallback. + + + + + +They also provide reverse lookup. For example, if you want to choose between all the tokens with the value `#e00`, you can do so by completing the prefix `e00`. + +#### Load snippets in VSCode + +Download the VSIX bundle that’s linked at the bottom of our [“Release v1.0.0”](https://github.com/RedHat-UX/red-hat-design-tokens/releases/tag/v1.0.0) page. + +#### Load snippets in Neovim + +Use LuaSnip to load snippets in Neovim: + + + + + +### Stylelint plugin + +Install the stylelint plugin to automatically correct token values in your files. + +See the [Stylelint Plugin README](https://github.com/RedHat-UX/red-hat-design-tokens/blob/main/plugins/stylelint/README.md) for more info. + +### 11ty plugin + +The experimental 11ty plugin lets you display token values in an 11ty site. + +### vim-hexokinase + +Vim users can load the [vim-hexokinase](https://github.com/RRethy/vim-hexokinase) plugin to display color swatches next to their encoded values in their editor. + +Use the following config (lua syntax, for Neovim users) to configure hexokinase to display color values next to color aliases like `{color.brand.red}`. + + + + + + + + + +{% feedback %} +

        Designers

        +

        To get started using our design system as a designer, go to the Designers page.

        +{% endfeedback %} \ No newline at end of file diff --git a/docs/get-started/developers/usage.md b/docs/get-started/developers/usage.md new file mode 100644 index 0000000000..b571d486fc --- /dev/null +++ b/docs/get-started/developers/usage.md @@ -0,0 +1,114 @@ +--- +layout: layout-with-subnav.njk +title: Usage +heading: Developers +tags: + - developers +permalink: /get-started/developers/usage/index.html +subNavCollection: sortedDevelopers +order: 20 +bodyClasses: element-docs +--- + +## Usage + +Now that you've installed the Red Hat Design System, here's more information about how to use the web components. + +### Using react wrappers + +React wrappers make it possible to use web components within React JSX files. Follow the steps below to learn how. + +#### 1. Initial setup + +We'll bootstrap our React app using Vite. It's possible to use other tools for this, but that is out of the scope of this tutorial. + + + + + +This command will ask you to provide the project name, framework, and variant. + +#### 2. Install the `@lit/react` library + +Use the following command: + + + + + +#### 3. Import elements and patterns + +After installing the `@lit/react` library, you can import elements and patterns +to your file. Below is an example of importing `` and ``, and +managing app state between them using react. + + + + + +### Using RHDS elements with Vue + +To get web components to work with Vue, it’s pretty easy and straightforward. Follow the steps below to use web components in a Vue app. + +#### 1. Initial setup + +Add these two lines at the top of the `main.js` file in the `/src/` directory. + + + + + +#### 2. Import elements and patterns + +Add the import statements to the top of the ` + + +## Other resources + +- [Red Hat Design System Wiki](https://github.com/RedHat-UX/red-hat-design-system/wiki) +- [Red Hat Brand Standard](https://www.redhat.com/en/about/brand/standards) + +{% feedback %} +

        Designers

        +

        To get started using our design system as a designer, go to the Designers page.

        +{% endfeedback %} diff --git a/docs/patterns/skip-navigation/index.md b/docs/patterns/skip-navigation/index.md index 17332fc7e0..0a9a16f182 100644 --- a/docs/patterns/skip-navigation/index.md +++ b/docs/patterns/skip-navigation/index.md @@ -6,6 +6,10 @@ tags: ## Overview + {% alert title="Warning", state="warning" %} + The skip navigation pattern is being deprecated. Please use the Skip link element instead. + {% endalert %} + Skip navigation is a styled link that appears at the top of a page when the Tab key is pressed. It bypasses the navigation and jumps users down to the main content when selected. ## Sample pattern diff --git a/docs/playgrounds.11ty.cjs b/docs/playgrounds.11ty.cjs index 2f3ba701ff..fd6272d0e3 100644 --- a/docs/playgrounds.11ty.cjs +++ b/docs/playgrounds.11ty.cjs @@ -8,7 +8,7 @@ module.exports = class Playground { pagination: { data: 'elements', size: 1, - } + }, }; } diff --git a/docs/release-notes.md b/docs/release-notes.md index 7bf3667f5d..5dc4f87783 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -19,6 +19,29 @@ We are continually making changes in order to improve and grow the Red Hat Desig
        +## Version 1.4.0 +Released April 22, 2024 + +### Highlights + +| Change | Notes {style="width: 70%" } | +| ------------------------------ | --------------------------------- | +| Added `` | Website status communicates the operational status of a website or domain using a status icon and link. It is usually located in the Footer component. | +| Added `` | Back to top component is a fragment link that allows users to quickly navigate to the top of a lengthy content. | A skip link is used to skip repetitive content on a page. It is hidden by default and can be activated by hitting the "Tab" key after loading/refreshing a page. | +| Added `` | A skip link is used to skip repetitive content on a page. It is hidden by default and can be activated by hitting the "Tab" key after loading/refreshing a page. | +| Updated `` | Added line numbers option, `Show more` toggle, copy and wrap actions, to `` | +| Updated `` | Improved focus accessibility for keyboard navigation users on firefox. | +| Updated `` | Improved focus accessibility on firefox. | +| Updated `` | Added an accents slot with placement options as inline and bottom. | +| Updated `` | Make sure alerts always have to correct (lightest) colour palette. | +| Updated `` | Allow tabs with long text content to fit into different-sized containers. | +| Updated PatternFly Elements tooling | [Patch update to dependencies](https://github.com/patternfly/patternfly-elements/releases/tag/%40patternfly%2Fpfe-core%403.0.0), including Lit version 3. | + +View version 1.4 release notes + +
        +
        + ## Version 1.3.0 Released January 11, 2024 diff --git a/docs/scss/components/_component-status-table.scss b/docs/scss/components/_component-status-table.scss index 9472262040..5a4100862b 100644 --- a/docs/scss/components/_component-status-table.scss +++ b/docs/scss/components/_component-status-table.scss @@ -2,6 +2,9 @@ display: flex; justify-content: space-between; align-items: flex-end; + p { + font-size: var(--rh-font-size-body-text-sm); + } } .component-status-list-container { diff --git a/elements/rh-accordion/context.ts b/elements/rh-accordion/context.ts new file mode 100644 index 0000000000..ba14bd0be7 --- /dev/null +++ b/elements/rh-accordion/context.ts @@ -0,0 +1,9 @@ +import type { RhAccordion } from './rh-accordion'; + +import { createContextWithRoot } from '@patternfly/pfe-core/functions/context.js'; + +export interface RhAccordionContext { + accents?: 'inline' | 'bottom'; +} + +export const context = createContextWithRoot(Symbol('rh-accordion-context')); diff --git a/elements/rh-accordion/demo/accents.html b/elements/rh-accordion/demo/accents.html new file mode 100644 index 0000000000..09ef6081e0 --- /dev/null +++ b/elements/rh-accordion/demo/accents.html @@ -0,0 +1,71 @@ +
        +

        Inline

        + + +

        Item One

        + Green + Red + Orange + Purple +
        + +

        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

        +
        + +

        Item Two

        + Green +
        + +

        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

        +
        + +

        Item Three

        + Red +
        + +

        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

        +
        +
        +
        + + +
        +

        Bottom

        + + +

        Item One

        + Green + Red + Orange + Purple +
        + +

        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

        +
        + +

        Item Two

        + Green +
        + +

        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

        +
        + +

        Item Three

        + Red +
        + +

        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

        +
        +
        +
        + + + + \ No newline at end of file diff --git a/elements/rh-accordion/docs/30-code.md b/elements/rh-accordion/docs/30-code.md index 91c0ebcbed..dff7558d55 100644 --- a/elements/rh-accordion/docs/30-code.md +++ b/elements/rh-accordion/docs/30-code.md @@ -1,6 +1,7 @@ {% renderInstall %}{% endrenderInstall %} -{% band header="Usage" %} +## Usage + ```html @@ -23,7 +24,6 @@ ``` -{% endband %} {% renderCodeDocs hideDescription=true %}{% endrenderCodeDocs %} diff --git a/elements/rh-accordion/docs/rh-accordion.md b/elements/rh-accordion/docs/rh-accordion.md index 51573e69b4..caec4ac616 100644 --- a/elements/rh-accordion/docs/rh-accordion.md +++ b/elements/rh-accordion/docs/rh-accordion.md @@ -2,7 +2,7 @@ {% endrenderOverview %} -{% band header="Usage" %}{% endband %} +## Usage {% renderSlots %}{% endrenderSlots %} @@ -14,4 +14,4 @@ {% renderCssCustomProperties %}{% endrenderCssCustomProperties %} -{% renderCssParts %}{% endrenderCssParts %} \ No newline at end of file +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/rh-accordion/rh-accordion-header.css b/elements/rh-accordion/rh-accordion-header.css index a87afee5b6..dd1cd2235a 100644 --- a/elements/rh-accordion/rh-accordion-header.css +++ b/elements/rh-accordion/rh-accordion-header.css @@ -101,12 +101,27 @@ a { span { overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: calc(100% - var(--rh-space-xl, 24px)); text-align: start; } +[part="container"] { + display: flex; + gap: var(--rh-space-xl, 24px); + container-type: inline-size; +} + +#header-container { + display: flex; + flex-direction: column; + gap: var(--rh-space-md, 8px); +} + +[part="accents"] { + display: flex; + flex-wrap: wrap; + gap: var(--rh-space-md, 8px); +} + #button[aria-expanded="true"] #icon { rotate: calc(var(--_isRTL, -1) * 180deg); } @@ -145,3 +160,9 @@ span { inset-block: 0; inset-inline-start: 0; } + +@container (min-width: 576px) { + #header-container:not(.bottom) { + flex-direction: row; + } +} diff --git a/elements/rh-accordion/rh-accordion-header.ts b/elements/rh-accordion/rh-accordion-header.ts index 1e1eebbe64..511111880c 100644 --- a/elements/rh-accordion/rh-accordion-header.ts +++ b/elements/rh-accordion/rh-accordion-header.ts @@ -1,9 +1,11 @@ import type { RhAccordion } from './rh-accordion.js'; +import type { RhAccordionContext } from './context.js'; import { html, LitElement } from 'lit'; import { classMap } from 'lit/directives/class-map.js'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; @@ -11,6 +13,10 @@ import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; import { DirController } from '../../lib/DirController.js'; import { colorContextConsumer, type ColorTheme } from '../../lib/context/color/consumer.js'; +import { consume } from '@lit/context'; + +import { context } from './context.js'; + import styles from './rh-accordion-header.css'; const isPorHeader = @@ -38,8 +44,8 @@ export class AccordionHeaderChangeEvent extends Event { * @slot * We expect the light DOM of the rh-accordion-header to be a heading level tag (h1, h2, h3, h4, h5, h6) * @slot accents - * These elements will appear inline with the accordion header, between the header and the chevron - * (or after the chevron and header in disclosure mode). + * These elements will appear inline by default with the header title, between the header and the chevron + * (or after the chevron and header in disclosure mode). There is an option to set the accents placement to bottom * * @fires {AccordionHeaderChangeEvent} change - when the open panels change * @@ -50,7 +56,10 @@ export class RhAccordionHeader extends LitElement { static readonly styles = [styles]; - static override readonly shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }; + static override readonly shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; @property({ type: Boolean, reflect: true }) expanded = false; @@ -58,10 +67,15 @@ export class RhAccordionHeader extends LitElement { @property({ reflect: true, attribute: 'heading-tag' }) headingTag?: string; + /** @deprecated */ @property({ reflect: true }) icon = 'angle-down'; @colorContextConsumer() private on?: ColorTheme; + @consume({ context, subscribe: true }) + @property({ attribute: false }) + private ctx?: RhAccordionContext; + #generatedHtag?: HTMLHeadingElement; #logger = new Logger(this); @@ -118,13 +132,20 @@ export class RhAccordionHeader extends LitElement { } #renderHeaderContent() { + const { accents } = this.ctx ?? {}; const headingText = this.headingText?.trim() ?? this.#header?.textContent?.trim(); + return html`
    + `; } } diff --git a/elements/rh-alert/test/rh-alert.spec.ts b/elements/rh-alert/test/rh-alert.spec.ts index dc7e158a5f..ba34b7ffb9 100644 --- a/elements/rh-alert/test/rh-alert.spec.ts +++ b/elements/rh-alert/test/rh-alert.spec.ts @@ -35,9 +35,9 @@ describe('', function() { it('should upgrade', async function() { const klass = customElements.get('rh-alert'); expect(element) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(RhAlert); + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(RhAlert); }); describe('dismissable ', async () => { diff --git a/elements/rh-audio-player/rh-audio-player.ts b/elements/rh-audio-player/rh-audio-player.ts index 9b5fc05346..a196208277 100644 --- a/elements/rh-audio-player/rh-audio-player.ts +++ b/elements/rh-audio-player/rh-audio-player.ts @@ -1,3 +1,5 @@ +import type { RhTooltip } from '../rh-tooltip/rh-tooltip.js'; + import { LitElement, html, type PropertyValues } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; @@ -25,7 +27,9 @@ import { RhAudioPlayerScrollingTextOverflow } from './rh-audio-player-scrolling- import buttonStyles from './rh-audio-player-button.css'; import rangeStyles from './rh-audio-player-range-styles.css'; import styles from './rh-audio-player.css'; -import { RhTooltip } from '../rh-tooltip/rh-tooltip.js'; + +import '../rh-surface/rh-surface.js'; +import '../rh-tooltip/rh-tooltip.js'; /** * An audio player plays audio clips in the browser and includes other features. @@ -167,7 +171,7 @@ export class RhAudioPlayer extends LitElement { 'subscribe': 'Subscribe', 'transcript': 'Transcript', 'autoscroll': 'Autoscroll', - 'download': 'Download' + 'download': 'Download', }; /** Audio's series name, e.g. Podcast series. */ @@ -245,6 +249,9 @@ export class RhAudioPlayer extends LitElement { #styles?: CSSStyleDeclaration; + // this is used inasmuch as children receive the context, + // but it doesn't need to be accessed outside the class + // eslint-disable-next-line no-unused-private-class-members #headings = new HeadingLevelContextProvider(this); #mediaElement?: HTMLAudioElement; @@ -263,11 +270,11 @@ export class RhAudioPlayer extends LitElement { #translation = new I18nController(this, { 'en': { - ...RhAudioPlayer.enUS + ...RhAudioPlayer.enUS, }, 'en-US': { - ...RhAudioPlayer.enUS - }, ...this.microcopy ?? {} + ...RhAudioPlayer.enUS, + }, ...this.microcopy ?? {}, }); #menufloat = new FloatingDOMController(this, { @@ -305,16 +312,24 @@ export class RhAudioPlayer extends LitElement { return !!this.layout?.startsWith('compact'); } - get #panels() { - return [this.#about, this.#subscribe, this.#transcript].filter(panel => !!panel); + get #panels(): ( + | { id: 'about'; panel: RhAudioPlayerAbout } + | { id: 'subscribe'; panel: RhAudioPlayerSubscribe } + | { id: 'transcript'; panel: RhTranscript } + )[] { + return [ + { id: 'about', panel: this.#about! } as const, + { id: 'subscribe', panel: this.#subscribe! } as const, + { id: 'transcript', panel: this.#transcript! } as const, + ].filter(x => !!x.panel); } get #hasMenu() { return ( - this.#panels.length > 1 || - !!this.mediaseries || - !!this.mediatitle || - (this._abouts?.length ?? 0) > 0 + this.#panels.length > 1 + || !!this.mediaseries + || !!this.mediatitle + || (this._abouts?.length ?? 0) > 0 ); } @@ -337,15 +352,18 @@ export class RhAudioPlayer extends LitElement { * gets list of allowable playback rates */ get #playbackRates() { - return [...Array(Math.round(this.#pbrMax / this.#pbrStep)).keys()].map(k=>k * this.#pbrStep + this.#pbrMin); + return [ + ...Array(Math.round(this.#pbrMax / this.#pbrStep)).keys()].map(k => + k * this.#pbrStep + this.#pbrMin + ); } /** * gets media media time if set */ get #mediaEnd() { - return (this.#mediaElement?.seekable?.end?.length || -1) > 0 && - this.#mediaElement?.seekable?.end(0) ? + return (this.#mediaElement?.seekable?.end?.length || -1) > 0 + && this.#mediaElement?.seekable?.end(0) ? this.#mediaElement?.seekable?.end(0) : false; } @@ -438,15 +456,15 @@ export class RhAudioPlayer extends LitElement { const muteicon = !this.muted ? RhAudioPlayer.icons.volumeMax : RhAudioPlayer.icons.volumeMuted; const mutelabel = !this.muted ? this.#translation.get('mute') : this.#translation.get('unmute'); const rewinddisabled = - !this.#mediaElement || - this.#readyState < 1 || - this.currentTime === 0 || - !this.#mediaEnd; + !this.#mediaElement + || this.#readyState < 1 + || this.currentTime === 0 + || !this.#mediaEnd; const forwarddisabled = - !this.#mediaElement || - this.#readyState < 1 || - this.currentTime === this.duration || - !this.#mediaEnd; + !this.#mediaElement + || this.#readyState < 1 + || this.currentTime === this.duration + || !this.#mediaEnd; const playlabel = !this.paused ? this.#translation.get('pause') : this.#translation.get('play'); @@ -607,11 +625,12 @@ export class RhAudioPlayer extends LitElement { style="${styleMap(styles)}" class="${classMap({ open })}" @keydown="${this.#onMenuKeydown}" - @focusout="${this.#onMenuFocusout}">${this.#panels.map(panel => !panel ? '' : html` - `)} `} @@ -632,18 +651,22 @@ export class RhAudioPlayer extends LitElement {