diff --git a/.changeset/remove-bundled-entrypoint.md b/.changeset/remove-bundled-entrypoint.md new file mode 100644 index 0000000000..e521614146 --- /dev/null +++ b/.changeset/remove-bundled-entrypoint.md @@ -0,0 +1,9 @@ +--- +"@patternfly/elements": major +--- + +Removed the bundled entrypoint (`pfe.min.js`). Import individual +elements directly instead, e.g. `import '@patternfly/elements/pf-button/pf-button.js'`. + +The bundled entrypoint caused custom element double-registration issues +and is not recommended for production use. diff --git a/.changeset/remove-deprecated-apis.md b/.changeset/remove-deprecated-apis.md new file mode 100644 index 0000000000..341e191b2f --- /dev/null +++ b/.changeset/remove-deprecated-apis.md @@ -0,0 +1,13 @@ +--- +"@patternfly/pfe-core": major +"@patternfly/elements": major +--- + +Removed deprecated APIs: + +- **`CascadeController`** and **`@cascades`** decorator are removed. + Use [`@lit/context`](https://lit.dev/docs/data/context/) instead for + sharing data between parent and child elements. +- **`SlotController.anonymous`** is removed. Use `SlotController.default` instead. +- **`pf-popover`**: the `close-label` attribute and `closeButtonLabel` property + are removed. Use `accessible-close-label` instead. diff --git a/.changeset/rename-all-elements.md b/.changeset/rename-all-elements.md new file mode 100644 index 0000000000..c97bd07b5e --- /dev/null +++ b/.changeset/rename-all-elements.md @@ -0,0 +1,12 @@ +--- +"@patternfly/elements": major +--- + +All elements are now versioned with a `pf-v5-` prefix. + +This is a breaking change. Update your HTML to use the new element names, +e.g. `` becomes ``, `` becomes +``, etc. + +CSS custom properties are also renamed from `--pf-c-*` to `--pf-v5-c-*`. +Global tokens (`--pf-global--*`) are unchanged. diff --git a/.changeset/wacky-jobs-lead.md b/.changeset/wacky-jobs-lead.md new file mode 100644 index 0000000000..1090297a93 --- /dev/null +++ b/.changeset/wacky-jobs-lead.md @@ -0,0 +1,6 @@ +--- +"@patternfly/pfe-tools": patch +--- + +Corrected usage of config for tagPrefix in `DocsPage` for alias + \ No newline at end of file diff --git a/.pfe.config.json b/.pfe.config.json index 8d99ac9ae4..02fa929c23 100644 --- a/.pfe.config.json +++ b/.pfe.config.json @@ -1,3 +1,4 @@ { - "renderTitleInOverview": false + "renderTitleInOverview": false, + "tagPrefix": "pf-v5" } diff --git a/README.md b/README.md index b049822886..4bb31cc4dc 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,10 @@ npm start npm run test:watch # Run a single test in watch mode. -npm run test:watch -- --files elements/pf-accordion/test/pf-accordion.spec.ts +npm run test:watch -- --files elements/pf-v5-accordion/test/pf-v5-accordion.spec.ts # Or multiple: -npm run test:watch -- --files 'elements/pf-{select,card}/test/*.spec.ts' +npm run test:watch -- --files 'elements/pf-v5-{select,card}/test/*.spec.ts' # Run all tests excluding react and vue tests. npm run test:watch diff --git a/core/pfe-core/controllers/cascade-controller.ts b/core/pfe-core/controllers/cascade-controller.ts deleted file mode 100644 index bde105cb07..0000000000 --- a/core/pfe-core/controllers/cascade-controller.ts +++ /dev/null @@ -1,169 +0,0 @@ -import type { ReactiveController, ReactiveElement } from 'lit'; - -import { bound } from '../decorators/bound.js'; -import { debounce } from '../functions/debounce.js'; -import { Logger } from './logger.js'; - -/** - * @deprecated use context, especially via `@patternfly/pfe-core/functions/context.js`; - */ -export interface Options { - properties: Partial>; - prefix?: string; -} - -/** - * @deprecated use context, especially via `@patternfly/pfe-core/functions/context.js`; - */ -export class CascadeController implements ReactiveController { - private class: typeof ReactiveElement; - - private logger: Logger; - - static instances: WeakMap> = - new WeakMap>(); - - mo: MutationObserver = new MutationObserver(this.parse); - - cache: Map = new Map(); - - constructor(public host: E, public options?: Options | undefined) { - this.class = host.constructor as typeof ReactiveElement; - this.logger = new Logger(this.host); - CascadeController.instances.set(host, this); - const properties = this.options?.properties ?? {} as Options['properties']; - for (const [propName, cascade] of Object.entries(properties)) { - this.initProp(propName, cascade); - } - host.addController(this); - this.cascadeProperties = debounce(this.cascadeProperties, 1); - } - - hostUpdated(): void { - this.cascadeProperties(); - } - - hostConnected(): void { - this.mo.observe(this.host, { attributes: true, childList: true }); - this.cascadeProperties(); - } - - hostDisconnected(): void { - this.mo.disconnect(); - } - - /** - * Handles the cascading of properties to nested components when new elements are added - * Attribute updates/additions are handled by the attribute callback - * @param [nodeList=this.host.children] - */ - cascadeProperties(nodeList: HTMLCollection | NodeList = this.host.children): void { - if (this.host.isConnected) { - const selectors = this.cache.keys(); - - // Find out if anything in the nodeList matches any of the observed selectors for cacading properties - if (!nodeList) { - return this._cascadeAttributes(selectors, this.cache); - } - - - for (const node of nodeList) { - // if this node has a match function (i.e., it's an HTMLElement, not a text node), - if (node instanceof Element) { - // see if it matches one of the selectors, otherwise drop it (like it's hot). - for (const selector of selectors) { - // console.log('_copyAttribute', name, value, el.getAttribute(name)); - if (node.matches(selector)) { - const attrNames = this.cache.get(selector); - // each selector can match multiple properties/attributes, so - // copy each of them - for (const attrName of attrNames ?? []) { - this._copyAttribute(attrName, node); - } - } - } - } - } - } - } - - /** - * Gets the configured attribute name for the decorated property, - * falling back to the lowercased property name, and caches the attribute name - * with it's designated child selectors for value-propagation on change - * @param propName - * @param cascade - */ - initProp(propName: string, cascade: string | string[]): void { - for (const nodeItem of [cascade].flat(Infinity).filter(Boolean) as string[]) { - const { attribute } = this.class.getPropertyOptions(propName); - - const attr = - typeof attribute === 'string' ? attribute - : propName.toLowerCase(); - - // Create an object with the node as the key and an array of attributes - // that are to be cascaded down to it - if (!this.cache.get(nodeItem)) { - this.cache.set(nodeItem, [attr]); - } else { - this.cache.get(nodeItem)?.push(attr); - } - } - } - - @bound private parse(mutations: MutationRecord[]) { - // Iterate over the mutation list, look for cascade updates - for (const mutation of mutations ?? []) { - // If a new node is added, attempt to cascade attributes to it - if (mutation.type === 'childList' && mutation.addedNodes.length) { - this.cascadeProperties(mutation.addedNodes); - } else if (mutation.type === 'attributes') { - this._cascadeAttributes(this.cache.keys(), this.cache); - } - } - } - - /** - * Copy the named attribute to a target element. - * @param name attr name - * @param el element - */ - private async _copyAttribute(name: string, el: Element) { - this.logger.log(`copying ${name} to ${el}`); - const value = this.host.getAttribute(name); - if (el.isConnected) { - if (value == null) { - el.removeAttribute(name); - } else { - el.setAttribute(name, value); - } - } - } - - private _cascadeAttributes(selectors: IterableIterator, set: this['cache']) { - for (const selector of selectors) { - for (const attr of set.get(selector) ?? []) { - this._cascadeAttribute(attr, selector); - } - } - } - - /** - * Trigger a cascade of the named attribute to any child elements that match - * the `to` selector. The selector can match elements in the light DOM and - * shadow DOM. - * @param name The name of the attribute to cascade (not necessarily the same as the property name). - * @param to A CSS selector that matches the elements that should received the cascaded attribute. The selector will be applied within `this` element's light and shadow DOM trees. - */ - private _cascadeAttribute(name: string, to: string) { - const recipients = [ - ...this.host.querySelectorAll(to), - ...this.host.shadowRoot?.querySelectorAll(to) ?? [], - ]; - - for (const node of recipients) { - this._copyAttribute(name, node); - } - } -} diff --git a/core/pfe-core/controllers/slot-controller-server.ts b/core/pfe-core/controllers/slot-controller-server.ts index 40b7ea649b..950a732109 100644 --- a/core/pfe-core/controllers/slot-controller-server.ts +++ b/core/pfe-core/controllers/slot-controller-server.ts @@ -7,8 +7,6 @@ import { export class SlotController implements SlotControllerPublicAPI { public static default = Symbol('default slot') satisfies symbol as symbol; - /** @deprecated use `default` */ - public static anonymous: symbol = this.default; static attribute = 'ssr-hint-has-slotted' as const; diff --git a/core/pfe-core/controllers/slot-controller.ts b/core/pfe-core/controllers/slot-controller.ts index 9578975a4b..296b0c218d 100644 --- a/core/pfe-core/controllers/slot-controller.ts +++ b/core/pfe-core/controllers/slot-controller.ts @@ -130,8 +130,6 @@ class SlotRecord { export class SlotController implements SlotControllerPublicAPI { public static default = Symbol('default slot') satisfies symbol as symbol; - /** @deprecated use `default` */ - public static anonymous: symbol = this.default; #slotRecords = new Map(); diff --git a/core/pfe-core/decorators.ts b/core/pfe-core/decorators.ts index 17bdfe6736..2acab8fb3e 100644 --- a/core/pfe-core/decorators.ts +++ b/core/pfe-core/decorators.ts @@ -1,5 +1,4 @@ export * from './decorators/bound.js'; -export * from './decorators/cascades.js'; export * from './decorators/deprecation.js'; export * from './decorators/initializer.js'; export * from './decorators/listen.js'; diff --git a/core/pfe-core/decorators/cascades.ts b/core/pfe-core/decorators/cascades.ts deleted file mode 100644 index 84134434c3..0000000000 --- a/core/pfe-core/decorators/cascades.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ReactiveElement } from 'lit'; - -import { CascadeController } from '../controllers/cascade-controller.js'; - -/** - * Cascades the decorated attribute to children - * @param items - * @deprecated use context, especially via `@patternfly/pfe-core/functions/context.js`; - */ -export function cascades(...items: string[]): PropertyDecorator { - return function(proto: T, key: string & keyof T) { - (proto.constructor as typeof ReactiveElement).addInitializer(x => { - const instance = x as ReactiveElement; - // You can have multiple `@cascades` decorators on an element - // and it will only get one CascadeController for all of them - if (!CascadeController.instances.has(instance)) { - CascadeController.instances.set(instance, new CascadeController(instance)); - } - - CascadeController.instances.get(instance)?.initProp(key, items); - }); - } as PropertyDecorator; -} diff --git a/core/pfe-core/package.json b/core/pfe-core/package.json index 7c5a920766..6b08802781 100644 --- a/core/pfe-core/package.json +++ b/core/pfe-core/package.json @@ -22,7 +22,6 @@ "./core.js": "./core.js", "./decorators.js": "./decorators.js", "./controllers/activedescendant-controller.js": "./controllers/activedescendant-controller.js", - "./controllers/cascade-controller.js": "./controllers/cascade-controller.js", "./controllers/css-variable-controller.js": "./controllers/css-variable-controller.js", "./controllers/floating-dom-controller.js": "./controllers/floating-dom-controller.js", "./controllers/internals-controller.js": "./controllers/internals-controller.js", @@ -41,7 +40,6 @@ "./controllers/timestamp-controller.js": "./controllers/timestamp-controller.js", "./controllers/tabs-controller.js": "./controllers/tabs-controller.js", "./decorators/bound.js": "./decorators/bound.js", - "./decorators/cascades.js": "./decorators/cascades.js", "./decorators/deprecation.js": "./decorators/deprecation.js", "./decorators/initializer.js": "./decorators/initializer.js", "./decorators/observed.js": "./decorators/observed.js", diff --git a/docs/_data/importMap.cjs b/docs/_data/importMap.cjs index 12279d1b23..3adacc0b80 100644 --- a/docs/_data/importMap.cjs +++ b/docs/_data/importMap.cjs @@ -1,6 +1,5 @@ const fs = require('node:fs'); const path = require('node:path'); -const { glob } = require('glob'); const packageLock = JSON.parse(fs.readFileSync(path.join( __dirname, @@ -13,107 +12,44 @@ function readPackageVersion(module) { return packageLock.packages[`node_modules/${module}`].version; } -const LIT_VERSION = readPackageVersion('lit'); -const FUSE_VERSION = readPackageVersion('fuse.js'); -const PRISM_VERSION = readPackageVersion('prismjs'); - -const LIT_DEPS = [ - { - target: `lit@${LIT_VERSION}`, - subpaths: [ - '.', - './decorators/query-all.js', - './decorators/query-assigned-nodes.js', - './decorators/query-assigned-elements.js', - './decorators/query-async.js', - './decorators/query.js', - './decorators/state.js', - './decorators/property.js', - './decorators/custom-element.js', - './directives/async-append.js', - './directives/async-replace.js', - './directives/cache.js', - './directives/class-map.js', - './directives/guard.js', - './directives/if-defined.js', - './directives/live.js', - './directives/ref.js', - './directives/repeat.js', - './directives/style-map.js', - './directives/template-content.js', - './directives/unsafe-html.js', - './directives/unsafe-svg.js', - './directives/until.js', - './async-directive.js', - './decorators.js', - './directive.js', - './directive-helpers.js', - './html.js', - './polyfill-support.js', - './static-html.js', - ], - }, - { - target: `@lit-labs/ssr-client`, - subpaths: [ - '.', - './lit-element-hydrate-support.js', - ], - }, +const LOCAL_PACKAGES = [ + '@patternfly/elements', + '@patternfly/pfe-core', + '@patternfly/pfe-tools', + '@patternfly/icons', ]; module.exports = async function() { - const { Generator } = await import('@jspm/generator'); - - const generator = new Generator({ - defaultProvider: 'jsdelivr', - env: ['production', 'browser', 'module'], + const { generate } = await import('@pwrs/mappa'); + + const map = await generate({ + dependencies: { + 'tslib': readPackageVersion('tslib'), + '@rhds/elements': readPackageVersion('@rhds/elements'), + 'prismjs': readPackageVersion('prismjs'), + 'element-internals-polyfill': readPackageVersion('element-internals-polyfill'), + 'fuse.js': readPackageVersion('fuse.js'), + 'lit': readPackageVersion('lit'), + '@lit-labs/ssr-client': readPackageVersion('@lit-labs/ssr-client'), + }, + }, { + cdn: 'jsdelivr', + exclude: LOCAL_PACKAGES, }); - await generator.install([ - 'tslib', - '@rhds/elements', - '@rhds/elements/rh-footer/rh-footer-universal.js', - `prismjs@${PRISM_VERSION}`, - 'element-internals-polyfill', - `fuse.js@${FUSE_VERSION}`, - ...LIT_DEPS, - ]); - - const map = generator.getMap(); - map.imports['/docs/zero-md.js'] = '/zero-md.js'; - map.imports['@patternfly/elements'] = '/pfe.min.js'; - map.imports['@patternfly/pfe-core'] = '/pfe.min.js'; - map.imports['@patternfly/icons/'] = '/assets/@patternfly/icons/'; - map.imports['@patternfly/pfe-core/decorators.js'] = '/pfe.min.js'; - map.imports['@patternfly/pfe-tools/environment.js'] = '/tools/environment.js'; - map.imports['lit/'] = map.imports.lit.replace('index.js', ''); - map.scopes['https://cdn.jsdelivr.net/'].lit = map.imports.lit; - map.scopes['https://cdn.jsdelivr.net/']['lit/'] = map.imports.lit.replace('index.js', ''); - - // add imports for imports under pfe-core - const pfeCoreImports = (await glob('./{functions,controllers,decorators}/*.ts', { - cwd: path.join(__dirname, '../../core/pfe-core'), - })) - .filter(x => !x.endsWith('.d.ts')) - .map(x => x.replace('.ts', '.js')); - for (const file of pfeCoreImports) { - map.imports[path.join('@patternfly/pfe-core', file)] = '/pfe.min.js'; - } - - map.imports['@patternfly/pfe-core/decorators.js'] = '/pfe.min.js'; - map.imports['@patternfly/pfe-core'] = '/pfe.min.js'; - - const elementsPath = path.join(__dirname, '..', '..', 'elements'); - for (const tagName of fs.readdirSync(elementsPath)) { - const elementPath = path.join(elementsPath, tagName); - if (fs.statSync(elementPath).isDirectory()) { - for (const fileName of fs.readdirSync(elementPath)) { - if (fileName.endsWith('.ts') && !fileName.endsWith('.d.ts')) { - map.imports[`@patternfly/elements/${tagName}/${fileName.replace('.ts', '')}.js`] = `/pfe.min.js`; - } + for (const key of Object.keys(map.imports)) { + for (const pkg of LOCAL_PACKAGES) { + if (key === pkg || key.startsWith(`${pkg}/`)) { + delete map.imports[key]; } } } + + map.imports['/docs/zero-md.js'] = '/zero-md.js'; + map.imports['@patternfly/elements/'] = '/assets/@patternfly/elements/'; + map.imports['@patternfly/pfe-core/'] = '/assets/@patternfly/pfe-core/'; + map.imports['@patternfly/pfe-core'] = '/assets/@patternfly/pfe-core/core.js'; + map.imports['@patternfly/pfe-tools/'] = '/assets/@patternfly/pfe-tools/'; + map.imports['@patternfly/icons/'] = '/assets/@patternfly/icons/'; return map; }; diff --git a/docs/_data/versions.json b/docs/_data/versions.json index 5a82922a6d..de78b59e13 100644 --- a/docs/_data/versions.json +++ b/docs/_data/versions.json @@ -1,9 +1,15 @@ [ + { + "version": "v5.0.0", + "slug": "v5", + "label": "v5", + "current": true + }, { "version": "v4.0.0", "slug": "v4", "label": "v4", - "current": true + "current": false }, { "version": "v3.0.0", diff --git a/docs/_includes/_nav.njk b/docs/_includes/_nav.njk index f1851d0dce..aeb39fa129 100644 --- a/docs/_includes/_nav.njk +++ b/docs/_includes/_nav.njk @@ -15,24 +15,24 @@ - - + Versions - + icon-set="fas">Versions + {% for version in versions %} {%- if version.current -%} {%- set prefix = '' -%} {%- else -%} {%- set prefix = '/' + version.slug -%} {%- endif %} - + {{ version.label }} - + {% endfor %} - - + + diff --git a/docs/_includes/layout-blog-index.njk b/docs/_includes/layout-blog-index.njk index 7070b36d5b..d6a1305f35 100644 --- a/docs/_includes/layout-blog-index.njk +++ b/docs/_includes/layout-blog-index.njk @@ -9,7 +9,7 @@ layout: layout-base.njk
{% for post in collections.blog | reverse %} - +

{{ post.data.title }}

{% if post.data.tagline %}

{{ post.data.tagline }}

{% endif %} @@ -18,7 +18,7 @@ layout: layout-base.njk {% endif %} Read Post -
+ {% endfor %}
@@ -34,7 +34,7 @@ layout: layout-base.njk gap: 2rem; } - pf-card::part(footer) { + pf-v5-card::part(footer) { justify-content: space-between; align-items: center; } diff --git a/docs/_includes/layout-blog.njk b/docs/_includes/layout-blog.njk index a2d93ae1b9..83b11a9212 100644 --- a/docs/_includes/layout-blog.njk +++ b/docs/_includes/layout-blog.njk @@ -28,13 +28,13 @@ layout: layout-base.njk {{ content | safe }} - Back to Top + Back to Top {% include '_foot.njk' %} `; + return content.replace(/]*type=["']module["'][^>]*>[\s\S]*?<\/script>/g, found => + `${script}\n${found}`); }); }; - diff --git a/docs/_plugins/pfe-assets.cjs b/docs/_plugins/pfe-assets.cjs index 240eee6b01..07965ef88a 100644 --- a/docs/_plugins/pfe-assets.cjs +++ b/docs/_plugins/pfe-assets.cjs @@ -4,16 +4,15 @@ const path = require('path'); /** * Generate a map of files per package which should be copied to the site dir * @param {object} [options] - * @param {string} [options.prefix='pfe'] element prefix e.g. 'pfe' for 'pf-button' + * @param {string} [options.prefix='pf-v5'] element prefix e.g. 'pf-v5' for 'pf-v5-button' */ function getFilesToCopy(options) { const cwd = process.cwd(); const prefix = `${(options?.prefix ?? 'pf').replace(/-$/, '')}-`; - const hasElements = fs.existsSync(path.join(cwd, 'elements')); const hasCore = fs.existsSync(path.join(cwd, 'core')); - if (!hasElements && !hasCore) { + if (!hasCore) { return null; } @@ -21,32 +20,19 @@ function getFilesToCopy(options) { [path.join(cwd, 'node_modules/element-internals-polyfill')]: 'element-internals-polyfill', }; - const tagNames = fs.readdirSync(path.join(cwd, 'elements')); const corePkgs = fs.readdirSync(path.join(cwd, 'core')); - // Copy all component and core files to _site - if (hasElements) { - Object.assign(files, Object.fromEntries(tagNames - .filter(x => !x.match(/node_modules|tsconfig|README\.md|(?:\.ts$)|(?:config\.js$)/)) - .map(dir => [ - `elements/${dir}`, - `components/${dir.replace(prefix, '')}`, - ]))); - } - - if (hasCore) { - Object.assign(files, Object.fromEntries(corePkgs.map(dir => [ - `core/${dir}`, - `core/${dir.replace(prefix, '')}`, - ]))); - } + Object.assign(files, Object.fromEntries(corePkgs.map(dir => [ + `core/${dir}`, + `core/${dir.replace(prefix, '')}`, + ]))); return files; } const DEMO_PATHS_RE = // eslint-disable-next-line @stylistic/max-len - /(?href|src)="\/(?elements|core)\/pf-(?.*)\/(?.*)\.(?[.\w]+)"/g; + /(?href|src)="\/(?elements|core)\/pf-v5-(?.*)\/(?.*)\.(?[.\w]+)"/g; /** * Replace paths in demo files from the dev SPA's format to 11ty's format @@ -63,18 +49,10 @@ function demoPaths(content) { } } -/** Generate a single-file bundle of all the repo's components and their dependencies */ -async function bundle() { - const { bundle } = await import('../../scripts/bundle.js'); - await bundle(); -} - module.exports = { configFunction(eleventyConfig, options) { eleventyConfig.addPassthroughCopy('docs/images/favicon.ico'); eleventyConfig.addPassthroughCopy('docs/bundle.{js,map,ts}'); - eleventyConfig.addPassthroughCopy('docs/pfe.min.{map,css}'); - eleventyConfig.addPassthroughCopy({ 'elements/pfe.min.*': '/' } ); eleventyConfig.addPassthroughCopy('docs/demo.{js,map,ts}'); eleventyConfig.addPassthroughCopy('docs/main.mjs'); eleventyConfig.addPassthroughCopy({ @@ -83,7 +61,43 @@ module.exports = { eleventyConfig.addPassthroughCopy({ 'node_modules/@patternfly/icons/': '/assets/@patternfly/icons/', }); + eleventyConfig.addPassthroughCopy({ + 'elements': '/assets/@patternfly/elements', + }); + eleventyConfig.addPassthroughCopy({ + './core/pfe-core': '/assets/@patternfly/pfe-core', + }); + eleventyConfig.addPassthroughCopy({ + 'tools/pfe-tools': '/assets/@patternfly/pfe-tools', + }); eleventyConfig.addPassthroughCopy('brand/**/*'); + + // Copy static assets (screenshots, demo images/css/js) from element folders + // to the site with the tag prefix stripped from the directory name. + // Markdown and HTML are handled by 11ty templates; this covers everything else. + const prefix = `${(options?.prefix ?? 'pf').replace(/-$/, '')}-`; + for (const dir of fs.readdirSync(path.join(process.cwd(), 'elements'))) { + const slug = dir.replace(prefix, ''); + const screenshot = path.join('elements', dir, 'docs', 'screenshot.png'); + if (fs.existsSync(screenshot)) { + eleventyConfig.addPassthroughCopy({ + [screenshot]: `/components/${slug}/docs/screenshot.png`, + }); + } + const demoDir = path.join(process.cwd(), 'elements', dir, 'demo'); + if (fs.existsSync(demoDir)) { + const assets = + fs.readdirSync(demoDir, { recursive: true }) + .filter(f => !String(f).endsWith('.html')); + for (const asset of assets) { + const src = path.join('elements', dir, 'demo', String(asset)); + eleventyConfig.addPassthroughCopy({ + [src]: `/components/${slug}/demo/${asset}`, + }); + } + } + } + const filesToCopy = getFilesToCopy(options); if (filesToCopy) { @@ -93,8 +107,5 @@ module.exports = { // The demo files are written primarily for the dev SPA (`npm start`), // so here we transform the paths found in those files to match the docs site's file structure eleventyConfig.addTransform('demo-paths', demoPaths); - - // create /docs/pfe.min.js - eleventyConfig.on('eleventy.before', () => bundle(options)); }, }; diff --git a/docs/_snippets/accordion-html.md b/docs/_snippets/accordion-html.md index e9f08de1fe..3b6cf174ca 100644 --- a/docs/_snippets/accordion-html.md +++ b/docs/_snippets/accordion-html.md @@ -1,21 +1,21 @@ ```html - - + +

Getting Started

-
- + +

Read our Getting started page to learn how to install and use PatternFly Elements.

-
- + +

HTML APIs

-
- + +

For more information on how to use each PatternFly element, read the component docs.

-
-
+ + ``` diff --git a/docs/_snippets/accordion-jsx.md b/docs/_snippets/accordion-jsx.md index 26f011cc0e..6ad0f942f6 100644 --- a/docs/_snippets/accordion-jsx.md +++ b/docs/_snippets/accordion-jsx.md @@ -3,7 +3,7 @@ import { Accordion, AccordionPanel, AccordionHeader, -} from "@patternfly/elements/react/pf-accordion/pf-accordion.js"; +} from "@patternfly/elements/react/pf-v5-accordion/pf-v5-accordion.js"; export default function App() { const data = [ diff --git a/docs/_snippets/accordion-ng.md b/docs/_snippets/accordion-ng.md index 6909712c05..5bd0ff0034 100644 --- a/docs/_snippets/accordion-ng.md +++ b/docs/_snippets/accordion-ng.md @@ -1,22 +1,22 @@ {%raw%} ```ts -import "@patternfly/elements/pf-accordion/pf-accordion.js"; +import "@patternfly/elements/pf-v5-accordion/pf-v5-accordion.js"; import { Component } from "@angular/core"; @Component({ selector: "app-root", template: ` - + - +

{{ item.header }}

-
- + +

{{ item.panel }}

-
+
-
+ `, }) export class AppComponent { diff --git a/docs/_snippets/accordion-svelte.md b/docs/_snippets/accordion-svelte.md index 7a6d2496cd..5b78629d8d 100644 --- a/docs/_snippets/accordion-svelte.md +++ b/docs/_snippets/accordion-svelte.md @@ -1,18 +1,18 @@ {%raw%} ```html - + {#each data as item} - +

{item.header}

-
- + +

{item.panel}

-
+ {/each} -
+ + - +

Card component

PatternFly Elements are custom HTML elements that work everywhere. The Card element has header and footer slots for things like titles and actions.

- More about pf-card -
+ More about pf-v5-card + ``` diff --git a/docs/_snippets/pf-bar-html.md b/docs/_snippets/pf-bar-html.md index c33f37ecff..5cfc26115f 100644 --- a/docs/_snippets/pf-bar-html.md +++ b/docs/_snippets/pf-bar-html.md @@ -3,7 +3,7 @@ PatternFly.org - + Contribute on Github \ No newline at end of file diff --git a/docs/components.md b/docs/components.md index dd9d4082fa..af41740e87 100644 --- a/docs/components.md +++ b/docs/components.md @@ -2,9 +2,9 @@ const CLASS_KEY = 'html-lit-react-snippets'; const LS_KEY = `${CLASS_KEY}-index`; document.addEventListener('expand', async function(event) { - const PfTabs = await customElements.whenDefined('pf-tabs'); - if (PfTabs.isExpandEvent(event)) { - const tabs = event.tab.closest('pf-tabs'); + const PfV5Tabs = await customElements.whenDefined('pf-v5-tabs'); + if (PfV5Tabs.isExpandEvent(event)) { + const tabs = event.tab.closest('pf-v5-tabs'); if (tabs.classList.contains(CLASS_KEY)) { await tabs.updateComplete; localStorage.setItem(LS_KEY, tabs.activeIndex); @@ -13,7 +13,7 @@ } }); async function update() { - for (const tabs of document.querySelectorAll(`pf-tabs.${CLASS_KEY}`)) { + for (const tabs of document.querySelectorAll(`pf-v5-tabs.${CLASS_KEY}`)) { await tabs.updateComplete; tabs.activeIndex = parseInt(localStorage.getItem(LS_KEY) ?? '0'); } diff --git a/docs/docs/develop/create.md b/docs/docs/develop/create.md index 2ab4de4588..355653aefe 100644 --- a/docs/docs/develop/create.md +++ b/docs/docs/develop/create.md @@ -35,8 +35,8 @@ tags: * Your element's name should be lowercase and needs to contain at least one hyphen. For rules on naming custom elements, refer to the W3C [Custom Elements Working Draft](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name). - * As an example, we'll create `pf-cool-element`. - * PatternFly Elements should be prefixed with `pf-`. However, prefix your + * As an example, we'll create `pf-v5-cool-element`. + * PatternFly Elements should be prefixed with `pf-v5-`. However, prefix your elements with whatever fits your project if you are using the generator outside of this project. @@ -45,18 +45,18 @@ tags: After answering, your new component will be created and bootstrapped in the repository. Once that's done, switch directories to the element you just created. We'll - `cd` into the `pf-cool-element` directory. + `cd` into the `pf-v5-cool-element` directory. ```bash - cd elements/pf-cool-element + cd elements/pf-v5-cool-element ``` Open your code editor to view the structure of the element. The element's source files are located directly in it's package root, in our case: - * `pf-cool-element.ts` - The element class declaration - * `pf-cool-element.css` - The element's CSS style module + * `pf-v5-cool-element.ts` - The element class declaration + * `pf-v5-cool-element.css` - The element's CSS style module The `demo` directory contains an HTML partial that you can edit to provide an interactive demo of your element. diff --git a/docs/docs/develop/css.md b/docs/docs/develop/css.md index c0b5a65636..312f635489 100644 --- a/docs/docs/develop/css.md +++ b/docs/docs/develop/css.md @@ -15,11 +15,11 @@ tags: {% band %} -We want the `pf-cool-element` to have a profile photo, a username, and a follow button. +We want the `pf-v5-cool-element` to have a profile photo, a username, and a follow button. Right now, it only contains the HTML structure, but we can style our element by updating our CSS to make it look the way we want. -We'll be working in the `pf-cool-element.css` file. +We'll be working in the `pf-v5-cool-element.css` file. The boilerplate stylesheet has a `:host` selector that makes our element display as a block element. @@ -69,12 +69,12 @@ After saving and viewing our demo page, our profile element looks much better. A couple of things to note: -1. The `:host` selector sets the styles of the container element ``. +1. The `:host` selector sets the styles of the container element ``. 2. The `button` styles are encapsulated within this element and will not bleed out, meaning that there's no chance these styles will affect other buttons on the page. Feeling confident that your element will always look the same when it's distributed is one of the main advantages of the shadow DOM. Check out the Styling section of [Shadow DOM v1: Self-Contained Web Components](https://developers.google.com/web/fundamentals/web-components/shadowdom#styling) to learn what else you can do when applying styles to the shadow DOM. -Now that our `pf-cool-element` is more appealing, we'll add the follow button's interaction +Now that our `pf-v5-cool-element` is more appealing, we'll add the follow button's interaction and fill in the profile photo. -We can accomplish both of these tasks by updating the `pf-cool-element.ts` file. +We can accomplish both of these tasks by updating the `pf-v5-cool-element.ts` file. Next up: Write your JavaScript diff --git a/docs/docs/develop/html.md b/docs/docs/develop/html.md index 7720cab6af..cb2f166518 100644 --- a/docs/docs/develop/html.md +++ b/docs/docs/develop/html.md @@ -25,17 +25,17 @@ If you are unfamiliar with TypeScript, read their getting-started documentation, and feel free to reach out to the PatternFly Elements team on our social media channels. -We'll edit the `pf-cool-element.ts` file to add some HTML to our element's +We'll edit the `pf-v5-cool-element.ts` file to add some HTML to our element's template. LitElements define their template in the `render()` method. The `render()` method can return a string, a number, a DOM node, etc, but usually it returns a `TemplateResult`, which is an `html` [tagged template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). -Let's turn `pf-cool-element` into a profile element that has a profile photo, a +Let's turn `pf-v5-cool-element` into a profile element that has a profile photo, a username, and a button to follow the user. -Here's the updated `render()` method in `pf-cool-element.ts`: +Here's the updated `render()` method in `pf-v5-cool-element.ts`: ```ts render() { @@ -50,15 +50,15 @@ render() { ``` We'll also need to update `/demo/index.html` -so that the user's name is passed into the slot that we added in `pf-cool-element.ts`: +so that the user's name is passed into the slot that we added in `pf-v5-cool-element.ts`: ```html - + - + Kyle Buchanan - + ``` Slots take the HTML from the light DOM and project it into the shadow DOM at a given location. diff --git a/docs/docs/develop/import-maps.md b/docs/docs/develop/import-maps.md index 9a823df0e3..48759e584d 100644 --- a/docs/docs/develop/import-maps.md +++ b/docs/docs/develop/import-maps.md @@ -31,14 +31,14 @@ Example: ``` @@ -65,7 +65,7 @@ Once you've generated your import map and added it to your project, you can use ``` diff --git a/docs/docs/develop/javascript.md b/docs/docs/develop/javascript.md index fe7e826cd3..02f3d25d01 100644 --- a/docs/docs/develop/javascript.md +++ b/docs/docs/develop/javascript.md @@ -48,7 +48,7 @@ Please note the TypeScript `#` character before the handlers' method name. This signals to the custom elements manifest analyzer to list this method as private, and marks it as such in the element's TypeScript definition file. This helps users of your element know which of its features are safe to use with confidence, -and which are likely to change without notice. For example, a user of `` would think twice about directly calling it's +and which are likely to change without notice. For example, a user of `` would think twice about directly calling it's `onClick()` method if it was marked as private. After saving your files, the demo page will refresh and you'll notice the start of your button interactivity. @@ -71,7 +71,7 @@ import { property } from 'lit/decorators/property.js' Then define the `following` boolean attribute on the element. ```ts -export class PfCoolElement extends LitElement { +export class PfV5CoolElement extends LitElement { static readonly styles = [style]; /** Whether the user follows this profile */ @@ -168,13 +168,13 @@ render() { Finally, we'll need to update our demo page (`/demo/index.html`) to include the `photo-url` attribute. Pass in an image URL to see that it's working. ```html - + Kyle Buchanan - + ``` -We can also modify `pf-cool-element.css` to adjust the background-size property -on `.pf-cool-element__profile`. +We can also modify `pf-v5-cool-element.css` to adjust the background-size property +on `.pf-v5-cool-element__profile`. The final result should look like this: @@ -186,7 +186,7 @@ That's all it takes, folks! To summarize, we built a web component that extends LitElement, then added an HTML template, custom styles, and interactivity. What's cool is that we've only scratched the surface of what's possible with custom elements and Lit. -For your reference, here's the final Javascript code for `pf-cool-element`: +For your reference, here's the final Javascript code for `pf-v5-cool-element`: ```ts import { LitElement, html } from 'lit'; @@ -194,14 +194,14 @@ import { customElement } from 'lit/decorators/custom-element.js'; import { property } from 'lit/decorators/property.js'; import { styleMap } from 'lit/directives/style-map.js'; -import styles from './pf-cool-element.css'; +import styles from './pf-v5-cool-element.css'; /** * Cool Element * @slot - Place element content here */ -@customElement('pf-cool-element') -export class PfCoolElement extends LitElement { +@customElement('pf-v5-cool-element') +export class PfV5CoolElement extends LitElement { static readonly styles = [styles]; /** Whether the user follows this profile */ @@ -230,7 +230,7 @@ export class PfCoolElement extends LitElement { declare global { interface HTMLElementTagNameMap { - 'pf-cool-element': PfCoolElement; + 'pf-v5-cool-element': PfV5CoolElement; } } ``` diff --git a/docs/docs/develop/setup.md b/docs/docs/develop/setup.md index 4ec40937b9..f16330be1a 100644 --- a/docs/docs/develop/setup.md +++ b/docs/docs/develop/setup.md @@ -59,14 +59,14 @@ tags: | --------------------- | ------------------------------------------ | --------------------------------------------------------------- | | `--directory` | Output directory | string [default: "/path/to/patternfly-elements"] | | `--silent` | Do not log anything to stdout | boolean [default: false] | -| `-n`, `--tagName` | Custom element tag name. e.g. `pf-button` | string | +| `-n`, `--tagName` | Custom element tag name. e.g. `pf-v5-button` | string | | `-p`, `--packageName` | NPM package scope. e.g. `@patternfly/elements`| string | | `--overwrite` | Overwrite files without prompting | boolean [default: false] | | `--help` | Show help | boolean | Example ```bash -npm run new -- --tagName pf-cool-element +npm run new -- --tagName pf-v5-cool-element ``` @@ -83,11 +83,11 @@ npm run new -- --tagName pf-cool-element - A file to write your unit tests - An HTML demo where you can show off your element and add examples for your development workflow - You may also add light DOM styles which can be loaded prior to [element defined](https://developer.mozilla.org/en-US/docs/Web/CSS/:defined) `pf-cool-element:not(:defined){...}`. An example use case would be to avoid above the fold layout shift. + You may also add light DOM styles which can be loaded prior to [element defined](https://developer.mozilla.org/en-US/docs/Web/CSS/:defined) `pf-v5-cool-element:not(:defined){...}`. An example use case would be to avoid above the fold layout shift. The light DOM CSS file uses a standard naming convention of: `{scope}-{component-name}--lightdom.css` - Example: `pf-cool-element--lightdom.css`. + Example: `pf-v5-cool-element--lightdom.css`. {% endband %} @@ -107,7 +107,7 @@ npm run new -- --tagName pf-cool-element Running that command launches the demo app in a new browser tab, and refreshes the page on save. From there you can navigate to the demo page of the element you're working on. - For example, if you want to preview the `pf-card` component, then navigate in the browser to `http://localhost:8000/demo/pf-card/`. + For example, if you want to preview the `pf-v5-card` component, then navigate in the browser to `http://localhost:8000/demo/pf-v5-card/`. {% endband %} {% band header="Testing" %} @@ -123,10 +123,10 @@ npm run new -- --tagName pf-cool-element ```bash # Run a single test in watch mode. - npm run test:watch --files "./elements/pf-button/test/pf-button.spec.ts" + npm run test:watch --files "./elements/pf-v5-button/test/pf-v5-button.spec.ts" # Or multiple: - npm run test:watch --files "./elements/pf-{avatar,card,tabs}/test/*.spec.ts" + npm run test:watch --files "./elements/pf-v5-{avatar,card,tabs}/test/*.spec.ts" ``` You can also run tests with a specific framework wrapper using: diff --git a/docs/docs/develop/structure.md b/docs/docs/develop/structure.md index 51d3190a26..f49d478525 100644 --- a/docs/docs/develop/structure.md +++ b/docs/docs/develop/structure.md @@ -44,20 +44,20 @@ Assuming the `npm run start` command started a server on port 8080, navigate to You're off to a good start! You have a new custom element that extends the base LitElement class. -Let's take a look at the `pf-cool-element.ts` file to see what we have. +Let's take a look at the `pf-v5-cool-element.ts` file to see what we have. ```ts import { LitElement, html } from 'lit'; import { customElement } from 'lit/decorators/custom-element.js'; -import styles from './pf-cool-element.css'; +import styles from './pf-v5-cool-element.css'; /** * Cool Element * @slot - Place element content here */ -@customElement('pf-cool-element') -export class PfCoolElement extends LitElement { +@customElement('pf-v5-cool-element') +export class PfV5CoolElement extends LitElement { static readonly styles = [styles]; render() { @@ -69,7 +69,7 @@ export class PfCoolElement extends LitElement { declare global { interface HTMLElementTagNameMap { - 'pf-cool-element': PfCoolElement; + 'pf-v5-cool-element': PfV5CoolElement; } } ``` @@ -90,7 +90,7 @@ Unlike PFE 1.0's `PFElement` base class, Lit template updates (i.e. renders) are For example, with `PFElement` we could handle the side-effects of our actions immediately: ```js -const element = document.querySelector('pf-tabs'); +const element = document.querySelector('pf-v5-tabs'); // Select the 2nd Tab element.selectIndex(1); // Side effects happen immediately, @@ -102,7 +102,7 @@ const active = element.querySelector('[aria-selected="true"]'); With `LitElement`, we must wait for our changes to apply before continuing: ```diff-js - const element = document.querySelector('pf-tabs'); + const element = document.querySelector('pf-v5-tabs'); // Select the 2nd Tab element.selectIndex(1); - // Side effects happen immediately, @@ -125,13 +125,13 @@ Decorators are a [proposed JavaScript language feature](https://github.com/tc39/ TypeScript implements an experimental version of the decorator language feature with a slightly different internal API. ```ts -@customElement('pf-cool-element') +@customElement('pf-v5-cool-element') ``` Third, we import an use our component's CSS styles ```ts -import styles from './pf-cool-element.css'; +import styles from './pf-v5-cool-element.css'; ``` ```ts diff --git a/docs/docs/develop/testing.md b/docs/docs/develop/testing.md index e9cdb2cc8f..0d99e6d978 100644 --- a/docs/docs/develop/testing.md +++ b/docs/docs/develop/testing.md @@ -14,7 +14,7 @@ tags: {% band %} -Let's write tests for `pf-cool-element`. +Let's write tests for `pf-v5-cool-element`. We rely on a few tools to ensure our element is reliable in production: @@ -27,7 +27,7 @@ If you followed the [Prerequisites](/docs/develop/setup/#prerequisites) in [Setu ### Test Setup -In the root of the element, there's a `/test` directory with an `pf-cool-element.spec.ts` file. This file will be where we add all of our tests. +In the root of the element, there's a `/test` directory with an `pf-v5-cool-element.spec.ts` file. This file will be where we add all of our tests. Let's add four stubs for the functionality we need to test in our test file: @@ -41,26 +41,26 @@ import { expect, html } from '@open-wc/testing/index-no-side-effects.js'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; // Import the element we're testing. -import { PfCoolElement } from '@patternfly/elements/pf-cool-element/pf-cool-element.js'; +import { PfV5CoolElement } from '@patternfly/elements/pf-v5-cool-element/pf-v5-cool-element.js'; // One element template, defined here, is used // in multiple tests. It's torn down and recreated each time. const template = html` - + Kyle Buchanan - + `; -describe('', function() { +describe('', function() { describe('simply instantiating', function() { - let element: PfCoolElement; + let element: PfV5CoolElement; it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-cool-element'); + element = await createFixture(html``); + const klass = customElements.get('pf-v5-cool-element'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfCoolElement); + .to.be.an.instanceOf(PfV5CoolElement); }); }); @@ -78,11 +78,11 @@ describe('', function() { }); ``` -You'll notice the `createFixture(element)` function. A [test fixture](https://open-wc.org/docs/testing/helpers/#test-fixtures) +You'll notice the `createFixture(element)` function. A [test fixture](https://open-wc.org/docs/testing/helpers/#test-fixtures) renders a piece of HTML and injects into the DOM so that you can test the behavior of your component. It returns the first DOM element from the template so that you can interact with it if needed. For example you can call functions, look up DOM nodes or inspect the rendered HTML. -The `` part signals to TypeScript that the result of calling this function is an instance of `PfCoolElement`; +The `` part signals to TypeScript that the result of calling this function is an instance of `PfV5CoolElement`; Test fixtures are async to ensure rendering is properly completed. @@ -93,38 +93,38 @@ Now that our setup is complete, we can start building our tests. ### Test Cases -Let's build out the 'pf-cool-element' tests. We'll use fixtures and `querySelector` to grab our element and include DOM API methods to interact with what we're testing. +Let's build out the 'pf-v5-cool-element' tests. We'll use fixtures and `querySelector` to grab our element and include DOM API methods to interact with what we're testing. Here is the full JavaScript code: ```ts import { expect, html } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfCoolElement } from '@patternfly/elements/pf-cool-element/pf-cool-element.js'; +import { PfV5CoolElement } from '@patternfly/elements/pf-v5-cool-element/pf-v5-cool-element.js'; // One element template, defined here, is used // in multiple tests. It's torn down and recreated each time. const template = html` - + Kyle Buchanan - + `; -describe('', function() { +describe('', function() { describe('simply instantiating', function() { - let element: PfCoolElement; + let element: PfV5CoolElement; it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-cool-element'); + element = await createFixture(html``); + const klass = customElements.get('pf-v5-cool-element'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfCoolElement); + .to.be.an.instanceOf(PfV5CoolElement); }); it('should allow a user to follow a profile', async function() { - const el = await createFixture(template); + const el = await createFixture(template); const button = el.shadowRoot!.querySelector('button')!; // Click the button. button.click(); @@ -151,7 +151,7 @@ describe('', function() { }); it('should set the state to follow if the following attribute is present', async function() { - const el = await createFixture(template); + const el = await createFixture(template); const button = el.shadowRoot!.querySelector('button')!; // Manually add the follow attribute. @@ -178,7 +178,7 @@ describe('', function() { }); it('should set a profile pic from the photo-url attribute', async function() { - const el = await createFixture(template); + const el = await createFixture(template); // Grab the `photo-url` attribute. const photoUrlAttribute = el.getAttribute('photo-url'); @@ -198,7 +198,7 @@ describe('', function() { You may notice we're accessing the `shadowRoot` here, available to our element by extending `LitElement` in the definition of our element. You can also access content in the `` of your element by using the `assignedNodes()` method. -We use a slot for the username in `pf-cool-element`, making it available to us in the array returned by `assignedNodes()`. +We use a slot for the username in `pf-v5-cool-element`, making it available to us in the array returned by `assignedNodes()`. ```javascript shadowRoot.querySelector('slot').assignedNodes()[0].textContent.trim(); @@ -206,10 +206,10 @@ shadowRoot.querySelector('slot').assignedNodes()[0].textContent.trim(); ### Run the Test -Lastly, we can run the test command below to see how we did. You can focus on this specific test so you're only running the tests for `pf-cool-element`. +Lastly, we can run the test command below to see how we did. You can focus on this specific test so you're only running the tests for `pf-v5-cool-element`. ```bash -npm run test:watch -- --files elements/**/pf-cool-element.spec.ts +npm run test:watch -- --files elements/**/pf-v5-cool-element.spec.ts ``` This command starts up web test runner and allows us to debug our test in the browser. @@ -229,7 +229,7 @@ Debugging the test in the browser should give you the following: Tests can also be run manually in browser by running the following command: ```bash -npm run test:watch -- --files elements/**/pf-cool-element.spec.ts --open``` +npm run test:watch -- --files elements/**/pf-v5-cool-element.spec.ts --open``` ### Testing against Vue and React @@ -238,13 +238,13 @@ Now that our vanilla JavaScript tests are passing, let's use Vue and React wrapp Run the same tests within React by using: ```bash -npm run test:react --files elements/**/pf-cool-element.spec.ts +npm run test:react --files elements/**/pf-v5-cool-element.spec.ts ``` Run the same tests within Vue with: ```bash -npm run test:vue --files elements/**/pf-cool-element.spec.ts +npm run test:vue --files elements/**/pf-v5-cool-element.spec.ts ``` This works exactly the same as the normal `npm run test:watch` command, the only difference is the fixture will be wrapped with React or Vue. @@ -256,7 +256,7 @@ Finally we can run the test "ci" command which will run the following: 3. Those same tests with Vue wrappers. ```bash -npm run test:ci --files elements/**/pf-cool-element.spec.ts +npm run test:ci --files elements/**/pf-v5-cool-element.spec.ts ``` ![Final test output](/images/develop/develop-testing-ci.png) @@ -265,7 +265,7 @@ Nice! All tests are working in Chrome and with React and Vue wrappers. 🎉 A quick note about the framework testing—the Vue and React tests are meant to be an initial first pass in those frameworks just to make sure that the functionality is working and that the component renders properly. -That's it for testing! Now that we've created our `pf-cool-element` and all of our code passes, the final step is to submit a pull request to get this merged. +That's it for testing! Now that we've created our `pf-v5-cool-element` and all of our code passes, the final step is to submit a pull request to get this merged. Next up: Open a pull request diff --git a/docs/docs/develop/troubleshooting.md b/docs/docs/develop/troubleshooting.md index 19d66298a3..a9c0480b6e 100644 --- a/docs/docs/develop/troubleshooting.md +++ b/docs/docs/develop/troubleshooting.md @@ -19,7 +19,7 @@ tags: If you experience issues when using or developing with PatternFly Elements, please open an issue on [GitHub](https://github.com/patternfly/patternfly-elements/issues/new/choose). - + Open an issue diff --git a/docs/framework-integration/index.md b/docs/framework-integration/index.md index 0a82c72956..f705f12b46 100644 --- a/docs/framework-integration/index.md +++ b/docs/framework-integration/index.md @@ -24,7 +24,7 @@ title: Framework integration
- +

Using PatternFly Elements in your React App @@ -32,8 +32,8 @@ title: Framework integration

To get web components to work with React it’s pretty easy and straightforward. If you’d like to follow along, go ahead and...

Read on Medium - - + +

Using PatternFly Elements in your Vue App @@ -41,8 +41,8 @@ title: Framework integration

To get web components to work with Vue, it’s pretty easy and straightforward. If you’d like to follow along, go ahead and...

Read on Medium - - + +

Using PatternFly Elements in your Angular App @@ -50,6 +50,6 @@ title: Framework integration

To get web components to work with Angular, there are a few steps that we need to take. If you’d like to follow along, go ahead and...

Read on Medium - +

diff --git a/docs/framework-integration/react.md b/docs/framework-integration/react.md index fc2d3f7ab6..c6de196478 100644 --- a/docs/framework-integration/react.md +++ b/docs/framework-integration/react.md @@ -128,16 +128,16 @@ tags: ```js import { useState } from "react"; - import { Button } from "@patternfly/elements/react/pf-button/pf-button.js"; - import { Card } from "@patternfly/elements/react/pf-card/pf-card.js"; - import { Switch } from "@patternfly/elements/react/pf-switch/pf-switch.js"; - import { Popover } from "@patternfly/elements/react/pf-popover/pf-popover.js"; - import { Tooltip } from "@patternfly/elements/react/pf-tooltip/pf-tooltip.js"; + import { Button } from "@patternfly/elements/react/pf-v5-button/pf-v5-button.js"; + import { Card } from "@patternfly/elements/react/pf-v5-card/pf-v5-card.js"; + import { Switch } from "@patternfly/elements/react/pf-v5-switch/pf-v5-switch.js"; + import { Popover } from "@patternfly/elements/react/pf-v5-popover/pf-v5-popover.js"; + import { Tooltip } from "@patternfly/elements/react/pf-v5-tooltip/pf-v5-tooltip.js"; import "./App.css"; ``` - Let’s use [`pf-button`][pf-button] and [`pf-card`][pf-card] component in the + Let’s use [`pf-v5-button`][pf-v5-button] and [`pf-v5-card`][pf-v5-card] component in the `App` function in the `App.tsx` file to see that our Card and Button are working. We are updating the local state and showing it in the UI after clicking the button. @@ -160,7 +160,7 @@ tags: ### Switch - Now we have a card and a button component, let's add [`pf-switch`][pf-switch] + Now we have a card and a button component, let's add [`pf-v5-switch`][pf-v5-switch] web component in our app. We will enable/disable the decrement button by clicking on the Switch button. @@ -208,7 +208,7 @@ tags: ### Tooltip Now we have a card, button, and switch component, let's add - [`pf-tooltip`][pf-tooltip] web component in our app. We will show the tooltip + [`pf-v5-tooltip`][pf-v5-tooltip] web component in our app. We will show the tooltip text on mouseover. ```js @@ -245,10 +245,10 @@ tags:
## Interacting with web components' APIs - Now we will add the [`pf-popover`][pf-popover] web component and open the + Now we will add the [`pf-v5-popover`][pf-v5-popover] web component and open the popover on the mouse over of the button. We will programmatically use the `show()` and `hide()` popover methods to show and hide the popover. **Note** - that these are methods on the `` DOM object. Because React apps do + that these are methods on the `` DOM object. Because React apps do not interact directly with the DOM, we will need to store a ref for the popover element, and call our methods on it's current element. @@ -353,10 +353,10 @@ tags: [ce-lifecycle]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks [react-lifecycle]: https://legacy.reactjs.org/docs/state-and-lifecycle.html [vite]: https://vitejs.dev/guide/#scaffolding-your-first-vite-project -[pf-button]: https://patternflyelements.org/components/button/ -[pf-card]: https://patternflyelements.org/components/card/ -[pf-switch]: https://patternflyelements.org/components/switch/ -[pf-tooltip]: https://patternflyelements.org/components/tooltip/ -[pf-popover]: https://patternflyelements.org/components/popover/ +[pf-v5-button]: https://patternflyelements.org/components/button/ +[pf-v5-card]: https://patternflyelements.org/components/card/ +[pf-v5-switch]: https://patternflyelements.org/components/switch/ +[pf-v5-tooltip]: https://patternflyelements.org/components/tooltip/ +[pf-v5-popover]: https://patternflyelements.org/components/popover/ [inng]: https://medium.com/patternfly-elements/using-patternfly-elements-web-components-in-your-angular-app-4b18b1c9c363 [invue]: https://patternflyelements.org/framework-integration/vue/ diff --git a/docs/framework-integration/vue.md b/docs/framework-integration/vue.md index b4a37e92e5..6f1008b623 100644 --- a/docs/framework-integration/vue.md +++ b/docs/framework-integration/vue.md @@ -41,8 +41,8 @@ tags: {% band header="Adding PatternFly Elements" %} With the setup complete, let’s add a couple of PatternFly Elements web components to our application to make sure everything is hooked up properly. - We’re going to add a card ([pf-card](/components/card)). Later, we’ll add an - accordion ([pf-accordion](/components/accordion)) and some CSS to help with + We’re going to add a card ([pf-v5-card](/components/card)). Later, we’ll add an + accordion ([pf-v5-accordion](/components/accordion)) and some CSS to help with our layout. Once again, if we were building this app locally, we’d install our @@ -52,7 +52,7 @@ tags: npm install --save @patternfly/elements ``` - But if you’re using CodeSandbox, just search for “@patternfly/pf-card” + But if you’re using CodeSandbox, just search for “@patternfly/elements” In our `HelloWorld.vue` file in the `/src/components/` directory, let’s add the import statements for our components to the top of the ` - {% endgenerateImportMap %} ``` To learn more about how to create importmaps, read our [creating an import map](/docs/develop/import-maps/) section, and go into more detail at [developer.mozilla.org](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) or the [import map specification](https://html.spec.whatwg.org/multipage/webappapis.html#import-maps). @@ -60,18 +65,18 @@ title: Get started When you have the import map script loaded on the page, you can add a [card component](/components/card) using html. ```html - +

Card header

-

This is the pf-card body.

- OK -
+

This is the pf-v5-card body.

+ OK + ``` - +

Card header

-

This is the pf-card body.

- OK -
+

This is the pf-v5-card body.

+ OK + ### Importmap and Markup @@ -86,18 +91,18 @@ Altogether your import map code could look something like this [Lit Playground D component to see which attributes are available. ```html - +

Card header

-

This is the pf-card body.

- OK -
+

This is the pf-v5-card body.

+ OK + ``` - +

Card header

-

This is the pf-card body.

- OK -
+

This is the pf-v5-card body.

+ OK + {% endband %} {% band header="Use CSS variables to customize or theme your components" %} @@ -111,14 +116,14 @@ Altogether your import map code could look something like this [Lit Playground D ```css /* your-page.css */ :root { - --pf-c-card--BackgroundColor: var(--pf-global--active-color--200, #bee1f4); + --pf-v5-c-card--BackgroundColor: var(--pf-global--active-color--200, #bee1f4); } ``` - +

Card header

-

This is the pf-card body.

- OK -
+

This is the pf-v5-card body.

+ OK + {% endband %} {% band header="Avoiding the flash of unstyled content (FOUC)" %} @@ -143,12 +148,12 @@ Altogether your import map code could look something like this [Lit Playground D --reveal-duration: 0.2s; } - pf-card { + pf-v5-card { opacity: 1; transition: opacity var(--reveal-duration) ease var(--reveal-delay); } - pf-card:not(:defined) { + pf-v5-card:not(:defined) { opacity: 0; } @@ -156,18 +161,18 @@ Altogether your import map code could look something like this [Lit Playground D - + - +

No FOUC

Content will remain hidden until component definitions are loaded.

-
+ ``` diff --git a/docs/home.css b/docs/home.css index 69b5bd65e9..f936eb299c 100644 --- a/docs/home.css +++ b/docs/home.css @@ -80,8 +80,8 @@ #lightw > a { grid-area: link; } #lightw > h2 { grid-area: h; } #lightw > p:not(.subtitle) { grid-area: p; } -#lightw > pf-card { grid-area: card; } -#lightw > pf-progress-stepper { grid-area: step; } +#lightw > pf-v5-card { grid-area: card; } +#lightw > pf-v5-progress-stepper { grid-area: step; } #lightw > pre { grid-area: snip; margin-block: 0; } #lightw, diff --git a/docs/index.html b/docs/index.html index 1d6ba8c8ce..6f5eacfc09 100644 --- a/docs/index.html +++ b/docs/index.html @@ -31,27 +31,27 @@

Lightweight

{% renderFile "./docs/_snippets/card-html.md" %} - +

Card component

PatternFly Elements are custom HTML elements that work everywhere. The Card element has header and footer slots for things like titles and actions.

- More about pf-card -
+ More about pf-v5-card + View the rest of the components - - Install - Build Your UI - + Install + Build Your UI + Ship! - + icon="optimize" icon-set="patternfly">Ship! +
@@ -60,43 +60,43 @@

Universal

Integrate PatternFly Elements with your framework of choice or use them by themselves.

PatternFly Elements integrate seamlessly with multiple frontend frameworks. Just install it - with npm install @patternfly/elements, + with npm install @patternfly/elements, or use PatternFly Elements on their own without a framework. It's up to you and the needs of your project.

- - HTML - {% renderFile "./docs/_snippets/accordion-html.md" %} + + HTML + {% renderFile "./docs/_snippets/accordion-html.md" %} - React - {% renderFile "./docs/_snippets/accordion-jsx.md" %} + React + {% renderFile "./docs/_snippets/accordion-jsx.md" %} - Vue - {% renderFile "./docs/_snippets/accordion-vue.md" %} + Vue + {% renderFile "./docs/_snippets/accordion-vue.md" %} - Angular - {% renderFile "./docs/_snippets/accordion-ng.md" %} + Angular + {% renderFile "./docs/_snippets/accordion-ng.md" %} - Svelte - {% renderFile "./docs/_snippets/accordion-svelte.md" %} - + Svelte + {% renderFile "./docs/_snippets/accordion-svelte.md" %} + - - + +

Getting Started

-
- + +

Read our Getting started page to learn how to install and use PatternFly Elements.

-
- + +

HTML APIs

-
- + +

For more information on how to use each PatternFly element, read the component docs.

-
-
+ + Learn how to integrate PatternFly Elements in your application diff --git a/docs/main.css b/docs/main.css index fcc2dfd43f..06bda39b54 100644 --- a/docs/main.css +++ b/docs/main.css @@ -291,24 +291,24 @@ header.band h1 { color: var(--pf-theme--color--white, #fff); } -.example-preview.pf-background-image pf-background-image { +.example-preview.pf-background-image pf-v5-background-image { position: absolute; z-index: -1; top: 0; left: 0; } -.example-preview.pf-background-image pf-background-image::part(container) { +.example-preview.pf-background-image pf-v5-background-image::part(container) { position: relative; } -.example-preview.pf-background-image pf-background-image, -.example-preview.pf-background-image pf-background-image::part(container) { +.example-preview.pf-background-image pf-v5-background-image, +.example-preview.pf-background-image pf-v5-background-image::part(container) { height: 100%; width: 100%; } -.example-preview.pf-background-image pf-background-image::part(container)::after { +.example-preview.pf-background-image pf-v5-background-image::part(container)::after { position: absolute; background-size: cover; } @@ -612,7 +612,7 @@ main.blog { border-right: .0625rem solid #d2d2d2; } -.site-navigation > pf-dropdown { +.site-navigation > pf-v5-dropdown { --pf-dropdown--MinWidth: 100%; width: var(--pf-dropdown--MinWidth); } @@ -813,7 +813,7 @@ code { min-width: 0; } -pf-card > h3 { +pf-v5-card > h3 { margin-top: 0; } diff --git a/docs/main.mjs b/docs/main.mjs index 215042ddc9..892f000ed8 100644 --- a/docs/main.mjs +++ b/docs/main.mjs @@ -1,11 +1,44 @@ import '@rhds/elements/rh-footer/rh-footer-universal.js'; import 'element-internals-polyfill'; -import { PfIcon } from '@patternfly/elements/pf-icon/pf-icon.js'; +import { PfV5Icon } from '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; +import '@patternfly/elements/pf-v5-accordion/pf-v5-accordion.js'; +import '@patternfly/elements/pf-v5-alert/pf-v5-alert.js'; +import '@patternfly/elements/pf-v5-avatar/pf-v5-avatar.js'; +import '@patternfly/elements/pf-v5-back-to-top/pf-v5-back-to-top.js'; +import '@patternfly/elements/pf-v5-background-image/pf-v5-background-image.js'; +import '@patternfly/elements/pf-v5-badge/pf-v5-badge.js'; +import '@patternfly/elements/pf-v5-banner/pf-v5-banner.js'; +import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; +import '@patternfly/elements/pf-v5-card/pf-v5-card.js'; +import '@patternfly/elements/pf-v5-chip/pf-v5-chip.js'; +import '@patternfly/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.js'; +import '@patternfly/elements/pf-v5-code-block/pf-v5-code-block.js'; +import '@patternfly/elements/pf-v5-dropdown/pf-v5-dropdown.js'; +import '@patternfly/elements/pf-v5-helper-text/pf-v5-helper-text.js'; +import '@patternfly/elements/pf-v5-hint/pf-v5-hint.js'; +import '@patternfly/elements/pf-v5-jump-links/pf-v5-jump-links.js'; +import '@patternfly/elements/pf-v5-label/pf-v5-label.js'; +import '@patternfly/elements/pf-v5-label-group/pf-v5-label-group.js'; +import '@patternfly/elements/pf-v5-modal/pf-v5-modal.js'; +import '@patternfly/elements/pf-v5-panel/pf-v5-panel.js'; +import '@patternfly/elements/pf-v5-popover/pf-v5-popover.js'; +import '@patternfly/elements/pf-v5-progress/pf-v5-progress.js'; +import '@patternfly/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.js'; +import '@patternfly/elements/pf-v5-search-input/pf-v5-search-input.js'; +import '@patternfly/elements/pf-v5-select/pf-v5-select.js'; +import '@patternfly/elements/pf-v5-spinner/pf-v5-spinner.js'; +import '@patternfly/elements/pf-v5-switch/pf-v5-switch.js'; +import '@patternfly/elements/pf-v5-table/pf-v5-table.js'; +import '@patternfly/elements/pf-v5-tabs/pf-v5-tabs.js'; +import '@patternfly/elements/pf-v5-text-area/pf-v5-text-area.js'; +import '@patternfly/elements/pf-v5-text-input/pf-v5-text-input.js'; +import '@patternfly/elements/pf-v5-tile/pf-v5-tile.js'; +import '@patternfly/elements/pf-v5-timestamp/pf-v5-timestamp.js'; +import '@patternfly/elements/pf-v5-tooltip/pf-v5-tooltip.js'; // if `/v2/` path load icons from static directory if (document.location.href.includes('/v2/')) { - // Workaround for bundled pf-icon: make icon imports absolute, instead of relative to the bundle - PfIcon.getIconUrl = (set, icon) => { + PfV5Icon.getIconUrl = (set, icon) => { const url = new URL(`/v2/components/icon/icons/${set}/${icon}.js`, import.meta.url); return url; }; diff --git a/docs/quick-start.md b/docs/quick-start.md index c12d87e226..8a6eb53376 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -40,11 +40,16 @@ production. font-family: "Red Hat Display"; } - {% generateImportMap %} + - {% endgenerateImportMap %}
@@ -53,69 +58,69 @@ production.

Card components

- +

Card 1

Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis laboriosam eum saepe eius tempora sequi eligendi repudiandae aspernatur beatae totam voluptatum facere unde, vitae inventore eveniet accusamus nulla recusandae aliquam.

More about PatternFly Elements -
- + +

Card 2

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque necessitatibus sapiente aliquam recusandae maxime consectetur magnam ipsa veniam expedita molestiae. Quis officia minima libero repellat laboriosam sit nemo porro laborum.

Get started -
- + +

Card 3

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Voluptate iusto laboriosam molestias, quidem ab voluptates nihil earum sed! Esse repellat quo ut numquam mollitia quis saepe aspernatur fuga error in!

About web components -
+ View all of the components

Accordion component

- - + +

Why do wizards need money if they could just create it?

-
- + +

There is legislation that decides what you can conjure and what you can not. Because things that you conjure out of thin air will not last, it is illegal in the wizarding world.

-
- + +

Why doesn't Harry have a portrait of his parents?

-
- + +

The characters in the portraits are not actually ghosts. They mainly are there just to repeat common phrases or serve as a general representation of the individual they depict. A portrait of his parents would not be of much help to Harry.

-
- + +

Why is Harry considered a half-blood if both of his parents could use magic?

-
- + +

Because Harry's grandparents were not able to do magic. This is generally frowned upon by those who consider themselves pure, such as the Malfoy's or other antagonists.

-
- + +

Is Hogwarts the only wizarding school?

-
- + +

No! It has been revealed that there are actually 11 long established and prestigious schools around the globe. These include Castelobruxo in the rainforest of Brazil, Durmstrang Institute (whereas nobody is certain of it’s whereabouts), and Ilvermorny, right here in the United States.

-
- + +

Where do the main characters work as adults?

-
- + +

Harry and Hermione are at the Ministry: he ends up leading the Auror department. Ron helps George at the joke shop and does very well. Ginny becomes a professional Quidditch player and then sportswriter for the Daily Prophet.

Read more about the characters

-
-
+ +

Tabs component

- - Users - Users - Containers - Containers - Database - Database - + + Users + Users + Containers + Containers + Database + Database +
diff --git a/elements/package.json b/elements/package.json index 3bab37ca1f..819ba17beb 100644 --- a/elements/package.json +++ b/elements/package.json @@ -5,72 +5,69 @@ "description": "PatternFly Elements", "customElements": "custom-elements.json", "type": "module", - "main": "./pfe.min.js", "module": "./pfe.js", "types": "./pfe.d.ts", "exports": { - ".": "./pfe.min.js", "./form-control.css": "./form-control.css", "./form-control.css.js": "./form-control.css.js", - "./pf-accordion/pf-accordion-header.js": "./pf-accordion/pf-accordion-header.js", - "./pf-accordion/pf-accordion-panel.js": "./pf-accordion/pf-accordion-panel.js", - "./pf-accordion/pf-accordion.js": "./pf-accordion/pf-accordion.js", - "./pf-alert/pf-alert.js": "./pf-alert/pf-alert.js", - "./pf-avatar/pf-avatar.js": "./pf-avatar/pf-avatar.js", - "./pf-back-to-top/pf-back-to-top.js": "./pf-back-to-top/pf-back-to-top.js", - "./pf-background-image/pf-background-image.js": "./pf-background-image/pf-background-image.js", - "./pf-badge/pf-badge.js": "./pf-badge/pf-badge.js", - "./pf-banner/pf-banner.js": "./pf-banner/pf-banner.js", - "./pf-button/pf-button.js": "./pf-button/pf-button.js", - "./pf-card/pf-card.js": "./pf-card/pf-card.js", - "./pf-chip/pf-chip.js": "./pf-chip/pf-chip.js", - "./pf-chip/pf-chip-group.js": "./pf-chip/pf-chip-group.js", - "./pf-clipboard-copy/pf-clipboard-copy.js": "./pf-clipboard-copy/pf-clipboard-copy.js", - "./pf-code-block/pf-code-block.js": "./pf-code-block/pf-code-block.js", - "./pf-dropdown/context.js": "./pf-dropdown/context.js", - "./pf-dropdown/pf-dropdown-group.js": "./pf-dropdown/pf-dropdown-group.js", - "./pf-dropdown/pf-dropdown-item.js": "./pf-dropdown/pf-dropdown-item.js", - "./pf-dropdown/pf-dropdown-menu.js": "./pf-dropdown/pf-dropdown-menu.js", - "./pf-dropdown/pf-dropdown.js": "./pf-dropdown/pf-dropdown.js", - "./pf-helper-text/pf-helper-text.js": "./pf-helper-text/pf-helper-text.js", - "./pf-hint/pf-hint.js": "./pf-hint/pf-hint.js", - "./pf-icon/pf-icon.js": "./pf-icon/pf-icon.js", - "./pf-jazz-hands/pf-jazz-hands.js": "./pf-jazz-hands/pf-jazz-hands.js", - "./pf-jump-links/pf-jump-links-item.js": "./pf-jump-links/pf-jump-links-item.js", - "./pf-jump-links/pf-jump-links-list.js": "./pf-jump-links/pf-jump-links-list.js", - "./pf-jump-links/pf-jump-links.js": "./pf-jump-links/pf-jump-links.js", - "./pf-label-group/pf-label-group.js": "./pf-label-group/pf-label-group.js", - "./pf-label/pf-label.js": "./pf-label/pf-label.js", - "./pf-select/pf-select.js": "./pf-select/pf-select.js", - "./pf-select/pf-listbox.js": "./pf-select/pf-listbox.js", - "./pf-select/pf-option-group.js": "./pf-select/pf-option-group.js", - "./pf-select/pf-option.js": "./pf-select/pf-option.js", - "./pf-modal/pf-modal.js": "./pf-modal/pf-modal.js", - "./pf-panel/pf-panel.js": "./pf-panel/pf-panel.js", - "./pf-progress-stepper/pf-progress-step.js": "./pf-progress-stepper/pf-progress-step.js", - "./pf-progress-stepper/pf-progress-stepper.js": "./pf-progress-stepper/pf-progress-stepper.js", - "./pf-progress/pf-progress.js": "./pf-progress/pf-progress.js", - "./pf-search-input/pf-search-input.js": "./pf-search-input/pf-search-input.js", - "./pf-spinner/pf-spinner.js": "./pf-spinner/pf-spinner.js", - "./pf-switch/pf-switch.js": "./pf-switch/pf-switch.js", - "./pf-table/context.js": "./pf-table/context.js", - "./pf-table/pf-caption.js": "./pf-table/pf-caption.js", - "./pf-table/pf-table.js": "./pf-table/pf-table.js", - "./pf-table/pf-tbody.js": "./pf-table/pf-tbody.js", - "./pf-table/pf-td.js": "./pf-table/pf-td.js", - "./pf-table/pf-th.js": "./pf-table/pf-th.js", - "./pf-table/pf-thead.js": "./pf-table/pf-thead.js", - "./pf-table/pf-tr.js": "./pf-table/pf-tr.js", - "./pf-tabs/context.js": "./pf-tabs/context.js", - "./pf-tabs/pf-tab-panel.js": "./pf-tabs/pf-tab-panel.js", - "./pf-tabs/pf-tab.js": "./pf-tabs/pf-tab.js", - "./pf-tabs/pf-tabs.js": "./pf-tabs/pf-tabs.js", - "./pf-text-area/pf-text-area.js": "./pf-text-area/pf-text-area.js", - "./pf-text-input/pf-text-input.js": "./pf-text-input/pf-text-input.js", - "./pf-tile/pf-tile.js": "./pf-tile/pf-tile.js", - "./pf-timestamp/pf-timestamp.js": "./pf-timestamp/pf-timestamp.js", - "./pf-tooltip/pf-tooltip.js": "./pf-tooltip/pf-tooltip.js", - "./pf-popover/pf-popover.js": "./pf-popover/pf-popover.js", + "./pf-v5-accordion/pf-v5-accordion-header.js": "./pf-v5-accordion/pf-v5-accordion-header.js", + "./pf-v5-accordion/pf-v5-accordion-panel.js": "./pf-v5-accordion/pf-v5-accordion-panel.js", + "./pf-v5-accordion/pf-v5-accordion.js": "./pf-v5-accordion/pf-v5-accordion.js", + "./pf-v5-alert/pf-v5-alert.js": "./pf-v5-alert/pf-v5-alert.js", + "./pf-v5-avatar/pf-v5-avatar.js": "./pf-v5-avatar/pf-v5-avatar.js", + "./pf-v5-back-to-top/pf-v5-back-to-top.js": "./pf-v5-back-to-top/pf-v5-back-to-top.js", + "./pf-v5-background-image/pf-v5-background-image.js": "./pf-v5-background-image/pf-v5-background-image.js", + "./pf-v5-badge/pf-v5-badge.js": "./pf-v5-badge/pf-v5-badge.js", + "./pf-v5-banner/pf-v5-banner.js": "./pf-v5-banner/pf-v5-banner.js", + "./pf-v5-button/pf-v5-button.js": "./pf-v5-button/pf-v5-button.js", + "./pf-v5-card/pf-v5-card.js": "./pf-v5-card/pf-v5-card.js", + "./pf-v5-chip/pf-v5-chip.js": "./pf-v5-chip/pf-v5-chip.js", + "./pf-v5-chip/pf-v5-chip-group.js": "./pf-v5-chip/pf-v5-chip-group.js", + "./pf-v5-clipboard-copy/pf-v5-clipboard-copy.js": "./pf-v5-clipboard-copy/pf-v5-clipboard-copy.js", + "./pf-v5-code-block/pf-v5-code-block.js": "./pf-v5-code-block/pf-v5-code-block.js", + "./pf-v5-dropdown/context.js": "./pf-v5-dropdown/context.js", + "./pf-v5-dropdown/pf-v5-dropdown-group.js": "./pf-v5-dropdown/pf-v5-dropdown-group.js", + "./pf-v5-dropdown/pf-v5-dropdown-item.js": "./pf-v5-dropdown/pf-v5-dropdown-item.js", + "./pf-v5-dropdown/pf-v5-dropdown-menu.js": "./pf-v5-dropdown/pf-v5-dropdown-menu.js", + "./pf-v5-dropdown/pf-v5-dropdown.js": "./pf-v5-dropdown/pf-v5-dropdown.js", + "./pf-v5-helper-text/pf-v5-helper-text.js": "./pf-v5-helper-text/pf-v5-helper-text.js", + "./pf-v5-hint/pf-v5-hint.js": "./pf-v5-hint/pf-v5-hint.js", + "./pf-v5-icon/pf-v5-icon.js": "./pf-v5-icon/pf-v5-icon.js", +"./pf-v5-jump-links/pf-v5-jump-links-item.js": "./pf-v5-jump-links/pf-v5-jump-links-item.js", + "./pf-v5-jump-links/pf-v5-jump-links-list.js": "./pf-v5-jump-links/pf-v5-jump-links-list.js", + "./pf-v5-jump-links/pf-v5-jump-links.js": "./pf-v5-jump-links/pf-v5-jump-links.js", + "./pf-v5-label-group/pf-v5-label-group.js": "./pf-v5-label-group/pf-v5-label-group.js", + "./pf-v5-label/pf-v5-label.js": "./pf-v5-label/pf-v5-label.js", + "./pf-v5-select/pf-v5-select.js": "./pf-v5-select/pf-v5-select.js", + "./pf-v5-select/pf-v5-listbox.js": "./pf-v5-select/pf-v5-listbox.js", + "./pf-v5-select/pf-v5-option-group.js": "./pf-v5-select/pf-v5-option-group.js", + "./pf-v5-select/pf-v5-option.js": "./pf-v5-select/pf-v5-option.js", + "./pf-v5-modal/pf-v5-modal.js": "./pf-v5-modal/pf-v5-modal.js", + "./pf-v5-panel/pf-v5-panel.js": "./pf-v5-panel/pf-v5-panel.js", + "./pf-v5-progress-stepper/pf-v5-progress-step.js": "./pf-v5-progress-stepper/pf-v5-progress-step.js", + "./pf-v5-progress-stepper/pf-v5-progress-stepper.js": "./pf-v5-progress-stepper/pf-v5-progress-stepper.js", + "./pf-v5-progress/pf-v5-progress.js": "./pf-v5-progress/pf-v5-progress.js", + "./pf-v5-search-input/pf-v5-search-input.js": "./pf-v5-search-input/pf-v5-search-input.js", + "./pf-v5-spinner/pf-v5-spinner.js": "./pf-v5-spinner/pf-v5-spinner.js", + "./pf-v5-switch/pf-v5-switch.js": "./pf-v5-switch/pf-v5-switch.js", + "./pf-v5-table/context.js": "./pf-v5-table/context.js", + "./pf-v5-table/pf-v5-caption.js": "./pf-v5-table/pf-v5-caption.js", + "./pf-v5-table/pf-v5-table.js": "./pf-v5-table/pf-v5-table.js", + "./pf-v5-table/pf-v5-tbody.js": "./pf-v5-table/pf-v5-tbody.js", + "./pf-v5-table/pf-v5-td.js": "./pf-v5-table/pf-v5-td.js", + "./pf-v5-table/pf-v5-th.js": "./pf-v5-table/pf-v5-th.js", + "./pf-v5-table/pf-v5-thead.js": "./pf-v5-table/pf-v5-thead.js", + "./pf-v5-table/pf-v5-tr.js": "./pf-v5-table/pf-v5-tr.js", + "./pf-v5-tabs/context.js": "./pf-v5-tabs/context.js", + "./pf-v5-tabs/pf-v5-tab-panel.js": "./pf-v5-tabs/pf-v5-tab-panel.js", + "./pf-v5-tabs/pf-v5-tab.js": "./pf-v5-tabs/pf-v5-tab.js", + "./pf-v5-tabs/pf-v5-tabs.js": "./pf-v5-tabs/pf-v5-tabs.js", + "./pf-v5-text-area/pf-v5-text-area.js": "./pf-v5-text-area/pf-v5-text-area.js", + "./pf-v5-text-input/pf-v5-text-input.js": "./pf-v5-text-input/pf-v5-text-input.js", + "./pf-v5-tile/pf-v5-tile.js": "./pf-v5-tile/pf-v5-tile.js", + "./pf-v5-timestamp/pf-v5-timestamp.js": "./pf-v5-timestamp/pf-v5-timestamp.js", + "./pf-v5-tooltip/pf-v5-tooltip.js": "./pf-v5-tooltip/pf-v5-tooltip.js", + "./pf-v5-popover/pf-v5-popover.js": "./pf-v5-popover/pf-v5-popover.js", "./react/*": "./react/*" }, "publishConfig": { diff --git a/elements/pf-accordion/README.md b/elements/pf-accordion/README.md deleted file mode 100644 index 3ef3d964c9..0000000000 --- a/elements/pf-accordion/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# PatternFly Elements Accordion - -`` is a web component that provides a collapsible accordion interface for sectioned content. - -Read more about Accordion in the [PatternFly Elements Accordion documentation](https://patternflyelements.org/components/accordion) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-accordion/pf-accordion.js'; -``` -## Usage - -```html - - -

Consetetur sadipscing elitr?

-
- -

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.

-
- -

Labore et dolore magna aliquyam erat?

-
- -

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.

-
-
-``` - diff --git a/elements/pf-accordion/demo/bordered.html b/elements/pf-accordion/demo/bordered.html deleted file mode 100644 index bad0d82148..0000000000 --- a/elements/pf-accordion/demo/bordered.html +++ /dev/null @@ -1,79 +0,0 @@ -
-
-

Bordered

- - - -
- - - -

Item one

-
- -

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

-
- -

Item two

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam ultrices, - faucibus erat id, maximus nunc.

-
- -

Item three

-
- -

Morbi vitae urna quis nunc convallis hendrerit. Aliquam congue orci quis ultricies tempus.

-
- -

Item four

-
- -

- Donec vel posuere orci. Phasellus quis tortor a ex hendrerit efficitur. Aliquam lacinia ligula pharetra, - sagittis ex ut, pellentesque diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere - cubilia Curae; - Vestibulum ultricies nulla nibh. Etiam vel dui fermentum ligula ullamcorper eleifend non quis tortor. Morbi - tempus ornare tempus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. - Mauris - et velit neque. Donec ultricies condimentum mauris, pellentesque imperdiet libero convallis convallis. Aliquam - erat volutpat. Donec rutrum semper tempus. Proin dictum imperdiet nibh, quis dapibus nulla. Integer sed - tincidunt - lectus, sit amet auctor eros. -

- Call To Action -
- -

Item five

-
- -

Vivamus finibus dictum ex id ultrices. Mauris dictum neque a iaculis blandit.

-
-
-
- - - - diff --git a/elements/pf-accordion/demo/content-attributes.html b/elements/pf-accordion/demo/content-attributes.html deleted file mode 100644 index b8373ab1f8..0000000000 --- a/elements/pf-accordion/demo/content-attributes.html +++ /dev/null @@ -1,36 +0,0 @@ -
- - - - -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam ultrices, - faucibus erat id, maximus nunc.

-
- - - -

Morbi vitae urna quis nunc convallis hendrerit. Aliquam congue orci quis ultricies tempus.

-
- - - -

Morbi vitae urna quis nunc convallis hendrerit. Aliquam congue orci quis ultricies tempus.

-
- -

Level One - Item 4, Slotted (no shadow header)

-
- -

Morbi vitae urna quis nunc convallis hendrerit. Aliquam congue orci quis ultricies tempus.

-
-
-
- - - - diff --git a/elements/pf-accordion/demo/index.html b/elements/pf-accordion/demo/index.html deleted file mode 100644 index c0d0b08395..0000000000 --- a/elements/pf-accordion/demo/index.html +++ /dev/null @@ -1,57 +0,0 @@ -
- - -

Item one

-
- -

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

-
- -

Item two

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam ultrices, - faucibus erat id, maximus nunc.

-
- -

Item three

-
- -

Morbi vitae urna quis nunc convallis hendrerit. Aliquam congue orci quis ultricies tempus.

-
- -

Item four

-
- -

- Donec vel posuere orci. Phasellus quis tortor a ex hendrerit efficitur. Aliquam lacinia ligula pharetra, - sagittis ex ut, pellentesque diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere - cubilia Curae; - Vestibulum ultricies nulla nibh. Etiam vel dui fermentum ligula ullamcorper eleifend non quis tortor. Morbi - tempus ornare tempus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. - Mauris - et velit neque. Donec ultricies condimentum mauris, pellentesque imperdiet libero convallis convallis. Aliquam - erat volutpat. Donec rutrum semper tempus. Proin dictum imperdiet nibh, quis dapibus nulla. Integer sed - tincidunt - lectus, sit amet auctor eros. -

-
- -

Item five

-
- -

Vivamus finibus dictum ex id ultrices. Mauris dictum neque a iaculis blandit.

-
-
-
- - - - diff --git a/elements/pf-accordion/demo/nested.html b/elements/pf-accordion/demo/nested.html deleted file mode 100644 index e7fd5ad477..0000000000 --- a/elements/pf-accordion/demo/nested.html +++ /dev/null @@ -1,120 +0,0 @@ -
- - -

Level One - Item one (single attr: true)

-
- - - -

Level Two - Item one (single attr: true)

-
- - - -

Level Three - Item one (single attr: false)

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam - ultrices, faucibus erat id, maximus nunc.

-
- -

Level Three - Item Two

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam - ultrices, faucibus erat id, maximus nunc.

-
- -

Level Three - Item Three

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam - ultrices, faucibus erat id, maximus nunc.

-
-
-
- -

Level Two - Item Two

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam - ultrices, faucibus erat id, maximus nunc.

-
- -

Level Two - Item Three

-
- - - -

Level Three - Item one (single attr: true)

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam - ultrices, faucibus erat id, maximus nunc.

-
- -

Level Three - Item Two

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam - ultrices, faucibus erat id, maximus nunc.

-
- -

Level Three - Item Three

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam - ultrices, faucibus erat id, maximus nunc.

-
-
-
-
-
- -

Level One - Item two

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam ultrices, - faucibus erat id, maximus nunc.

-
- -

Level One - Item three

-
- -

Morbi vitae urna quis nunc convallis hendrerit. Aliquam congue orci quis ultricies tempus.

-
- -

Level One - Item four

-
- -

- Donec vel posuere orci. Phasellus quis tortor a ex hendrerit efficitur. Aliquam lacinia ligula pharetra, - sagittis ex ut, pellentesque diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere - cubilia Curae; - Vestibulum ultricies nulla nibh. Etiam vel dui fermentum ligula ullamcorper eleifend non quis tortor. Morbi - tempus ornare tempus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. - Mauris - et velit neque. Donec ultricies condimentum mauris, pellentesque imperdiet libero convallis convallis. Aliquam - erat volutpat. Donec rutrum semper tempus. Proin dictum imperdiet nibh, quis dapibus nulla. Integer sed - tincidunt - lectus, sit amet auctor eros. -

-
- -

Level One - Item five

-
- -

Vivamus finibus dictum ex id ultrices. Mauris dictum neque a iaculis blandit.

-
-
-
- - - - diff --git a/elements/pf-accordion/demo/single-expanded-panel.html b/elements/pf-accordion/demo/single-expanded-panel.html deleted file mode 100644 index 97b4e07160..0000000000 --- a/elements/pf-accordion/demo/single-expanded-panel.html +++ /dev/null @@ -1,49 +0,0 @@ - - -

Level One - Item one

-
- -

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

-
- -

Level One - Item two

-
- -

Vivamus et tortor sed arcu congue vehicula eget et diam. Praesent nec dictum lorem. Aliquam id diam ultrices, - faucibus erat id, maximus nunc.

-
- -

Level One - Item three

-
- -

Morbi vitae urna quis nunc convallis hendrerit. Aliquam congue orci quis ultricies tempus.

-
- -

Level One - Item four

-
- -

- Donec vel posuere orci. Phasellus quis tortor a ex hendrerit efficitur. Aliquam lacinia ligula pharetra, - sagittis ex ut, pellentesque diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere - cubilia Curae; - Vestibulum ultricies nulla nibh. Etiam vel dui fermentum ligula ullamcorper eleifend non quis tortor. Morbi - tempus ornare tempus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. - Mauris - et velit neque. Donec ultricies condimentum mauris, pellentesque imperdiet libero convallis convallis. Aliquam - erat volutpat. Donec rutrum semper tempus. Proin dictum imperdiet nibh, quis dapibus nulla. Integer sed - tincidunt - lectus, sit amet auctor eros. -

-
- -

Level One - Item five

-
- -

Vivamus finibus dictum ex id ultrices. Mauris dictum neque a iaculis blandit.

-
-
- - diff --git a/elements/pf-accordion/docs/pf-accordion.md b/elements/pf-accordion/docs/pf-accordion.md deleted file mode 100644 index 12d7f530eb..0000000000 --- a/elements/pf-accordion/docs/pf-accordion.md +++ /dev/null @@ -1,92 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - - - -

Laboris sunt qui dolor consectetur excepteur in aliqua ipsum?

-
- -

Culpa adipisicing sunt dolor ullamco dolor duis in ad commodo.

- Call to action -
- -

Anim est tempor fugiat pariatur laborum deserunt ex mollit aliquip?

-
- -

Ullamco ullamco sint ex id magna elit deserunt dolore nostrud eu et dolore est Lorem. Esse laborum do ut consectetur occaecat proident et nostrud ut nostrud veniam officia Lorem.

-
- -

Nostrud ad sit commodo nostrud?

-
- -

Nisi veniam tempor reprehenderit laboris amet laborum et do ut. Veniam eiusmod aliquip ullamco quis esse laborum Lorem exercitation consequat.

-
- -

Reprehenderit cupidatat labore?

-
- -

Magna incididunt aliquip consectetur dolor adipisicing amet cillum officia nostrud. Elit exercitation voluptate aute nostrud.

- Call to action -
-
- -{% endrenderOverview %} - -{% band header="Usage" %} - {% htmlexample %} - - - -

Laboris sunt qui dolor consectetur excepteur in aliqua ipsum?

-
- -

Culpa adipisicing sunt dolor ullamco dolor duis in ad commodo.

- Call to action -
- -

Anim est tempor fugiat pariatur laborum deserunt ex mollit aliquip?

-
- -

Ullamco ullamco sint ex id magna elit deserunt dolore nostrud eu et dolore est Lorem. Esse laborum do ut consectetur occaecat proident et nostrud ut nostrud veniam officia Lorem.

-
- -

Nostrud ad sit commodo nostrud?

-
- -

Nisi veniam tempor reprehenderit laboris amet laborum et do ut. Veniam eiusmod aliquip ullamco quis esse laborum Lorem exercitation consequat.

-
- -

Reprehenderit cupidatat labore?

-
- -

Magna incididunt aliquip consectetur dolor adipisicing amet cillum officia nostrud. Elit exercitation voluptate aute nostrud.

- Call to action -
-
- - {% endhtmlexample %} -{% endband %} - -{% renderSlots %} - ### Default slot in pf-accordion-header - - We expect the light DOM of the `pf-accordion-header` to be a heading level tag - (h1, h2, h3, h4, h5, h6) - - ### Default slot in pf-accordion-panel - - Add the content for your accordion panel here. -{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderEvents for='pf-accordion-header', header='Events on `pf-accordion-header`' %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-accordion/pf-accordion-header.css b/elements/pf-accordion/pf-accordion-header.css deleted file mode 100644 index 6ff1d60026..0000000000 --- a/elements/pf-accordion/pf-accordion-header.css +++ /dev/null @@ -1,169 +0,0 @@ -:host { - --pf-icon--size: var(--pf-c-accordion__toggle--IconSize, 10px); - - /** - * Sets the font color for the accordion header. - */ - color: var(--pf-c-accordion__toggle--Color, var(--pf-global--Color--100, #151515)); - background-color: var(--pf-global--BackgroundColor--100, #ffffff); -} - -:host([large]) { - /** Sets the top padding for the accordion header. */ - --pf-c-accordion__toggle--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** Sets the right padding for the accordion header. */ - --pf-c-accordion__toggle--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** Sets the bottom padding for the accordion header. */ - --pf-c-accordion__toggle--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** Sets the left padding for the accordion header. */ - --pf-c-accordion__toggle--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); - /** Sets the font family for the accordion header. */ - --pf-c-accordion__toggle--FontFamily: - var(--pf-global--FontFamily--redhat-updated--heading--sans-serif, - "RedHatDisplayUpdated", - "Overpass", - overpass, - helvetica, - arial, - sans-serif - ); - /** Sets the font size for the accordion header. */ - --pf-c-accordion__toggle--FontSize: var(--pf-global--FontSize--xl, 1.25rem); - --pf-c-accordion__toggle--hover-text--Color: var(--pf-global--Color--100, #151515); - --pf-c-accordion__toggle--active-text--Color: var(--pf-global--Color--100, #151515); - --pf-c-accordion__toggle--active-text--FontWeight: var(--pf-global--FontWeight--normal, 400); - --pf-c-accordion__toggle--focus-text--Color: var(--pf-global--Color--100, #151515); - --pf-c-accordion__toggle--focus-text--FontWeight: var(--pf-global--FontWeight--normal, 400); - --pf-c-accordion__toggle--expanded-text--Color: var(--pf-global--Color--100, #151515); - --pf-c-accordion__toggle--expanded-text--FontWeight: var(--pf-global--FontWeight--normal, 400); - --pf-icon--size: var(--pf-c-accordion__toggle--IconSize, 12px); -} - -#heading { - font-weight: var(--pf-c-accordion__toggle--FontWeight, var(--pf-global--FontWeight--normal, 400)); - font-size: 100%; - padding: 0; - margin: 0; -} - -button, -a { - cursor: pointer; -} - -.toggle, -.toggle:before, -.toggle:after { - padding: 0; - margin: 0; - /** Sets the background color for the accordion header toggle element. */ - background-color: var(--pf-c-accordion__toggle--BackgroundColor, transparent); -} - -.icon { - /** - * Sets the transition animation for the accordion header. - */ - transition: var(--pf-c-accordion__toggle-icon--Transition, 0.2s ease-in 0s); -} - -.toggle { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - border: 0; - padding: - var(--pf-c-accordion__toggle--PaddingTop, var(--pf-global--spacer--md, 0.5rem)) - var(--pf-c-accordion__toggle--PaddingRight, var(--pf-global--spacer--md, 1rem)) - var(--pf-c-accordion__toggle--PaddingBottom, var(--pf-global--spacer--sm, 0.5rem)) - var(--pf-c-accordion__toggle--PaddingLeft, var(--pf-global--spacer--md, 1rem)); - font-family: - var(--pf-c-accordion__toggle--FontFamily, - var(--pf-global--FontFamily--redhat-updated--heading--sans-serif, - "RedHatTextUpdated", - helvetica, - arial, - sans-serif)); - font-size: var(--pf-c-accordion__toggle--FontSize, var(--pf-global--FontSize--lg, 1rem)); - /** - * Sets the font weight for the accordion header. - */ - font-weight: var(--pf-c-accordion__toggle--FontWeight, var(--pf-global--FontWeight--normal, 400)); - color: var(--pf-c-accordion__toggle--Color, var(--pf-global--Color--100, #151515)); -} - -.toggle[aria-expanded="true"] { - --pf-c-accordion__toggle--after--BackgroundColor: - /** - * Sets the hover expanded before background color for the accordion header. - */ - var( - --pf-c-accordion__toggle--expanded--before--BackgroundColor, - var( - --pf-global--primary-color--100, - #0066cc - ) - ); -} - -.toggle:after { - top: var(--pf-c-accordion__toggle--before--Top, -1px); - /** - * Sets the sidebar width for the accordion header. - */ - width: var(--pf-c-accordion__toggle--before--Width, var(--pf-global--BorderWidth--lg, 3px)); - /** - * Sets the background color for the after element for the accordion header toggle element. - */ - background-color: var(--pf-c-accordion__toggle--after--BackgroundColor, transparent); - content: ""; - position: absolute; - bottom: 0; - left: 0; -} - -span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - /** - * Sets the max width for the text inside the accordion header. - */ - max-width: var(--pf-c-accordion__toggle-text--MaxWidth, - calc(100% - var(--pf-global--spacer--lg, 1.5rem))); -} - -.toggle[aria-expanded="true"] .icon { - /** - * Sets the expanded icon rotation degrees for the accordion header. - */ - rotate: var(--pf-c-accordion__toggle--expanded-icon--Rotate, 90deg); -} - -.toggle:hover, -.toggle:active, -.toggle:focus { - /** - * Sets the active background color for the accordion header. - */ - background-color: - var(--pf-c-accordion__toggle--active--BackgroundColor, - var(--pf-global--BackgroundColor--200, #f0f0f0)); -} - -.toggle:hover span, -.toggle:focus span, -.toggle:active span { - /** - * Sets the active text color for the accordion header. - */ - color: var(--pf-c-accordion__toggle--active-text--Color, var(--pf-global--link--Color, #0066cc)); -} - -.toggle:focus span, -.toggle:active span { - font-weight: var(--pf-c-accordion__toggle--active-text--FontWeight, - var(--pf-global--FontWeight--semi-bold, 700)); -} diff --git a/elements/pf-accordion/pf-accordion-header.ts b/elements/pf-accordion/pf-accordion-header.ts deleted file mode 100644 index ed59c62ba9..0000000000 --- a/elements/pf-accordion/pf-accordion-header.ts +++ /dev/null @@ -1,179 +0,0 @@ -import type { PfAccordion } from './pf-accordion.js'; - -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; -import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; - -import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; - -import style from './pf-accordion-header.css'; - -import '@patternfly/elements/pf-icon/pf-icon.js'; - -const isPorHeader = - (el: Node): el is HTMLElement => - el instanceof HTMLElement && !!el.tagName.match(/P|^H[1-6]/); - -export class PfAccordionHeaderChangeEvent extends Event { - declare target: PfAccordionHeader; - constructor( - public expanded: boolean, - public toggle: PfAccordionHeader, - public accordion: PfAccordion - ) { - super('change', { bubbles: true }); - } -} - -/** - * Accordion Header - * @fires {AccordionHeaderChangeEvent} change - when the open panels change - * - */ -@customElement('pf-accordion-header') -export class PfAccordionHeader extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - @property({ reflect: true }) bordered?: 'true' | 'false'; - - @property({ reflect: true }) icon?: string; - - @property({ reflect: true, attribute: 'icon-set' }) iconSet?: string; - - @property({ type: Boolean, reflect: true }) expanded = false; - - @property({ reflect: true, attribute: 'heading-text' }) headingText?: string; - - @property({ reflect: true, attribute: 'heading-tag' }) headingTag?: string; - - #generatedHtag?: HTMLHeadingElement; - - #logger = new Logger(this); - - #header?: HTMLElement; - - #slots = new SlotController(this, 'accents', null); - - override connectedCallback(): void { - super.connectedCallback(); - this.id ||= getRandomId(this.localName); - this.#initHeader(); - } - - override render(): TemplateResult<1> { - const headingText = this.headingText?.trim() ?? this.#header?.textContent?.trim(); - const content = html` - - `; - switch (this.headingTag) { - case 'h1': return html`

${content}

`; - case 'h2': return html`

${content}

`; - case 'h3': return html`

${content}

`; - case 'h4': return html`

${content}

`; - case 'h5': return html`
${content}
`; - case 'h6': return html`
${content}
`; - default: return content; - } - } - - async #initHeader() { - if (this.headingText) { - this.headingTag ||= 'h3'; - } - this.#header = this.#getOrCreateHeader(); - - // prevent double-logging - if (this.#header !== this.#generatedHtag) { - this.#generatedHtag = undefined; - } - - do { - await this.updateComplete; - } while (!await this.updateComplete); - - // Remove the hidden attribute after upgrade - this.hidden = false; - } - - #getOrCreateHeader(): HTMLElement | undefined { - // Check if there is no nested element or nested textNodes - if (!this.firstElementChild && !this.firstChild) { - return void this.#logger.warn('No header content provided'); - } else if (this.firstElementChild) { - const [heading, ...otherContent] = Array.from(this.children) - .filter((x): x is HTMLElement => !x.hasAttribute('slot') && isPorHeader(x)); - - // If there is no content inside the slot, return empty with a warning - // else, if there is more than 1 element in the slot, capture the first h-tag - if (!heading) { - return void this.#logger.warn('No heading information was provided.'); - } else if (otherContent.length) { - this.#logger.warn('Heading currently only supports 1 tag; extra tags will be ignored.'); - } - return heading; - } else { - if (!this.#generatedHtag) { - this.#logger.warn('Header should contain at least 1 heading tag for correct semantics.'); - } - this.#generatedHtag = document.createElement('h3'); - - // If a text node was provided but no semantics, default to an h3 - // otherwise, incorrect semantics were used, create an H3 and try to capture the content - if (this.firstChild?.nodeType === Node.TEXT_NODE) { - this.#generatedHtag.textContent = this.firstChild.textContent; - } else { - this.#generatedHtag.textContent = this.textContent; - } - - return this.#generatedHtag; - } - } - - #onClick() { - const expanded = !this.expanded; - const acc = this.closest('pf-accordion'); - if (acc) { - this.dispatchEvent(new PfAccordionHeaderChangeEvent(expanded, this, acc)); - } - } -} - - -declare global { - interface HTMLElementTagNameMap { - 'pf-accordion-header': PfAccordionHeader; - } -} diff --git a/elements/pf-accordion/pf-accordion-panel.css b/elements/pf-accordion/pf-accordion-panel.css deleted file mode 100644 index b418e27695..0000000000 --- a/elements/pf-accordion/pf-accordion-panel.css +++ /dev/null @@ -1,85 +0,0 @@ -:host { - display: none; - position: relative; - overflow: hidden; - will-change: height; - color: var(--pf-global--Color--100, #151515); - /** Background color for accordion panel */ - background-color: - var( - --pf-c-accordion--BackgroundColor, - var(--pf-global--BackgroundColor--light-100, #ffffff) - ); -} - -.body { - /** Padding for accordion panel body */ - padding: - /** Top padding for panel content */ - var(--pf-c-accordion__panel-body--PaddingTop, var(--pf-global--spacer--sm, 0.5rem)) - /** Right padding for panel content */ - var(--pf-c-accordion__panel-body--PaddingRight, var(--pf-global--spacer--md, 1rem)) - /** Bottom padding for panel content */ - var(--pf-c-accordion__panel-body--PaddingBottom, var(--pf-global--spacer--sm, 0.5rem)) - /** Left padding for panel content */ - var(--pf-c-accordion__panel-body--PaddingLeft, var(--pf-global--spacer--md, 1rem)); -} - -.body:after { - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - /** Before width for panel content */ - width: var(--pf-c-accordion__panel-body--before--Width, var(--pf-global--BorderWidth--lg, 3px)); - /** Background color for panel content before element */ - background-color: var(--pf-c-accordion__panel-body--before--BackgroundColor, transparent); -} - -:host([large]) { - --pf-c-accordion__panel-body--PaddingTop: - var(--pf-c-accordion--m-display-lg__expanded-content-body--PaddingTop, 0); - --pf-c-accordion__panel-body--PaddingRight: - var(--pf-c-accordion--m-display-lg__expanded-content-body--PaddingRight, 1rem); - --pf-c-accordion__panel-body--PaddingBottom: - var(--pf-c-accordion--m-display-lg__expanded-content-body--PaddingBottom, 1.5rem); - --pf-c-accordion__panel-body--PaddingLeft: - var(--pf-c-accordion--m-display-lg__expanded-content-body--PaddingLeft, 1.5rem); - --pf-c-accordion__panel--FontSize: - var(--pf-c-accordion--m-display-lg__expanded-content--FontSize, 1rem); - --pf-c-accordion__panel--Color: - var(--pf-c-accordion--m-display-lg__expanded-content--Color, #151515); -} - -:host([large]) .body:last-child { - --pf-c-accordion__panel-body--PaddingBottom: - var(--pf-c-accordion--m-display-lg__expanded-content-body--last-child--PaddingBottom, 1.5rem); -} - -.content { - /** Font color for panel content */ - color: var(--pf-c-accordion__panel--Color, var(--pf-global--Color--dark-200, #6a6e73)); - /** Font size for panel content */ - font-size: var(--pf-c-accordion__panel--FontSize, var(--pf-global--FontSize--sm, 0.875rem)); -} - -:host([fixed]) { - overflow-y: auto; - /** Maximum height for panel content when fixed */ - max-height: var(--pf-c-accordion__panel--m-fixed--MaxHeight, 9.375rem); -} - -:host([expanded]) { - display: block; - position: relative; -} - -.content[expanded], -:host([expanded]) .content { - /** Sidebar color for panel when expanded */ - --pf-c-accordion__panel-body--before--BackgroundColor: - var( - --pf-c-accordion__panel--content-body--before--BackgroundColor, - var(--pf-global--primary-color--100, #0066cc)); -} diff --git a/elements/pf-accordion/pf-accordion-panel.ts b/elements/pf-accordion/pf-accordion-panel.ts deleted file mode 100644 index 545c90523a..0000000000 --- a/elements/pf-accordion/pf-accordion-panel.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; - -import style from './pf-accordion-panel.css'; - -/** - * Accordion Panel - * @slot - Panel content - */ -@customElement('pf-accordion-panel') -export class PfAccordionPanel extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - @property({ type: Boolean, reflect: true }) expanded = false; - - @property({ reflect: true }) bordered?: 'true' | 'false'; - - override connectedCallback(): void { - super.connectedCallback(); - this.id ||= getRandomId(this.localName); - this.setAttribute('role', 'region'); - } - - override render(): TemplateResult<1> { - return html` -
-
-
- -
-
-
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-accordion-panel': PfAccordionPanel; - } -} diff --git a/elements/pf-accordion/pf-accordion.css b/elements/pf-accordion/pf-accordion.css deleted file mode 100644 index cd9d61c94f..0000000000 --- a/elements/pf-accordion/pf-accordion.css +++ /dev/null @@ -1,153 +0,0 @@ -:host { - /** BackgroundColor for the accordion */ - --pf-c-accordion--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - /** PaddingTop for the toggle */ - --pf-c-accordion__toggle--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** PaddingRight for the toggle */ - --pf-c-accordion__toggle--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the toggle */ - --pf-c-accordion__toggle--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); - /** PaddingLeft for the toggle */ - --pf-c-accordion__toggle--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** BackgroundColor for the toggle before element */ - --pf-c-accordion__toggle--before--BackgroundColor: transparent; - /** Top position for the toggle before element */ - --pf-c-accordion__toggle--before--Top: 0; - /** BackgroundColor for the toggle on hover */ - --pf-c-accordion__toggle--hover--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - /** BackgroundColor for the toggle on focus */ - --pf-c-accordion__toggle--focus--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - /** BackgroundColor for the toggle when active */ - --pf-c-accordion__toggle--active--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - /** Width for the toggle before element */ - --pf-c-accordion__toggle--before--Width: var(--pf-global--BorderWidth--lg, 3px); - /** BackgroundColor for the toggle before element when expanded */ - --pf-c-accordion__toggle--m-expanded--before--BackgroundColor: var(--pf-global--primary-color--100, #06c); - /** MaxWidth for the toggle text */ - --pf-c-accordion__toggle-text--MaxWidth: calc(100% - var(--pf-global--spacer--lg, 1.5rem)); - /** Color for the toggle text on hover */ - --pf-c-accordion__toggle--hover__toggle-text--Color: var(--pf-global--link--Color, #06c); - /** Color for the toggle text when active */ - --pf-c-accordion__toggle--active__toggle-text--Color: var(--pf-global--link--Color, #06c); - /** FontWeight for the toggle text when active */ - --pf-c-accordion__toggle--active__toggle-text--FontWeight: var(--pf-global--FontWeight--semi-bold, 700); - /** Color for the toggle text on focus */ - --pf-c-accordion__toggle--focus__toggle-text--Color: var(--pf-global--link--Color, #06c); - /** FontWeight for the toggle text on focus */ - --pf-c-accordion__toggle--focus__toggle-text--FontWeight: var(--pf-global--FontWeight--semi-bold, 700); - /** Color for the toggle text when expanded */ - --pf-c-accordion__toggle--m-expanded__toggle-text--Color: var(--pf-global--link--Color, #06c); - /** FontWeight for the toggle text when expanded */ - --pf-c-accordion__toggle--m-expanded__toggle-text--FontWeight: var(--pf-global--FontWeight--semi-bold, 700); - /** Transition for the toggle icon */ - --pf-c-accordion__toggle-icon--Transition: .2s ease-in 0s; - /** Rotate value for the toggle icon when expanded */ - --pf-c-accordion__toggle--m-expanded__toggle-icon--Rotate: 90deg; - /** Color for the expanded content */ - --pf-c-accordion__expanded-content--Color: var(--pf-global--Color--200, #6a6e73); - /** FontSize for the expanded content */ - --pf-c-accordion__expanded-content--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** BackgroundColor for the expanded content body before element when expanded */ - --pf-c-accordion__expanded-content--m-expanded__expanded-content-body--before--BackgroundColor: var(--pf-global--primary-color--100, #06c); - /** MaxHeight for the expanded content when fixed */ - --pf-c-accordion__expanded-content--m-fixed--MaxHeight: 9.375rem; - /** PaddingTop for the expanded content body */ - --pf-c-accordion__expanded-content-body--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** PaddingRight for the expanded content body */ - --pf-c-accordion__expanded-content-body--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the expanded content body */ - --pf-c-accordion__expanded-content-body--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); - /** PaddingLeft for the expanded content body */ - --pf-c-accordion__expanded-content-body--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** PaddingTop for nested expanded content body */ - --pf-c-accordion__expanded-content-body--expanded-content-body--PaddingTop: 0; - /** BackgroundColor for the expanded content body before element */ - --pf-c-accordion__expanded-content-body--before--BackgroundColor: transparent; - /** Width for the expanded content body before element */ - --pf-c-accordion__expanded-content-body--before--Width: var(--pf-global--BorderWidth--lg, 3px); - /** Top position for the expanded content body before element */ - --pf-c-accordion__expanded-content-body--before--Top: 0; - /** PaddingTop for the toggle in large display mode */ - --pf-c-accordion--m-display-lg__toggle--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** PaddingRight for the toggle in large display mode */ - --pf-c-accordion--m-display-lg__toggle--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the toggle in large display mode */ - --pf-c-accordion--m-display-lg__toggle--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** PaddingLeft for the toggle in large display mode */ - --pf-c-accordion--m-display-lg__toggle--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); - /** FontFamily for the toggle in large display mode */ - --pf-c-accordion--m-display-lg__toggle--FontFamily: var(--pf-global--FontFamily--heading--sans-serif, "RedHatDisplay", "Overpass", overpass, helvetica, arial, sans-serif); - /** FontSize for the toggle in large display mode */ - --pf-c-accordion--m-display-lg__toggle--FontSize: var(--pf-global--FontSize--xl, 1.25rem); - /** Color for the toggle text on hover in large display mode */ - --pf-c-accordion--m-display-lg__toggle--hover__toggle-text--Color: var(--pf-global--Color--100, #151515); - /** Color for the toggle text when active in large display mode */ - --pf-c-accordion--m-display-lg__toggle--active__toggle-text--Color: var(--pf-global--Color--100, #151515); - /** FontWeight for the toggle text when active in large display mode */ - --pf-c-accordion--m-display-lg__toggle--active__toggle-text--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** Color for the toggle text on focus in large display mode */ - --pf-c-accordion--m-display-lg__toggle--focus__toggle-text--Color: var(--pf-global--Color--100, #151515); - /** FontWeight for the toggle text on focus in large display mode */ - --pf-c-accordion--m-display-lg__toggle--focus__toggle-text--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** Color for the toggle text when expanded in large display mode */ - --pf-c-accordion--m-display-lg__toggle--m-expanded__toggle-text--Color: var(--pf-global--Color--100, #151515); - /** FontWeight for the toggle text when expanded in large display mode */ - --pf-c-accordion--m-display-lg__toggle--m-expanded__toggle-text--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** FontSize for the expanded content in large display mode */ - --pf-c-accordion--m-display-lg__expanded-content--FontSize: var(--pf-global--FontSize--md, 1rem); - /** Color for the expanded content in large display mode */ - --pf-c-accordion--m-display-lg__expanded-content--Color: var(--pf-global--Color--100, #151515); - /** PaddingTop for the expanded content body in large display mode */ - --pf-c-accordion--m-display-lg__expanded-content-body--PaddingTop: 0; - /** PaddingRight for the expanded content body in large display mode */ - --pf-c-accordion--m-display-lg__expanded-content-body--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the expanded content body in large display mode */ - --pf-c-accordion--m-display-lg__expanded-content-body--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the last child of expanded content body in large display mode */ - --pf-c-accordion--m-display-lg__expanded-content-body--last-child--PaddingBottom: var(--pf-global--spacer--lg, 1.5rem); - /** PaddingLeft for the expanded content body in large display mode */ - --pf-c-accordion--m-display-lg__expanded-content-body--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); - /** BorderTopWidth for the bordered variant */ - --pf-c-accordion--m-bordered--BorderTopWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderTopColor for the bordered variant */ - --pf-c-accordion--m-bordered--BorderTopColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** Top position for the toggle before element in bordered variant */ - --pf-c-accordion--m-bordered__toggle--before--Top: calc(-1 * var(--pf-global--BorderWidth--sm, 1px)); - /** BorderColor for the toggle after element in bordered variant */ - --pf-c-accordion--m-bordered__toggle--after--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** BorderTopWidth for the toggle after element in bordered variant */ - --pf-c-accordion--m-bordered__toggle--after--BorderTopWidth: 0; - /** BorderBottomWidth for the toggle after element in bordered variant */ - --pf-c-accordion--m-bordered__toggle--after--BorderBottomWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderBottomWidth for the last child after element in bordered expanded content */ - --pf-c-accordion--m-bordered__expanded-content--m-expanded__expanded-content-body--last-child--after--BorderBottomWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderBottomColor for the last child after element in bordered expanded content */ - --pf-c-accordion--m-bordered__expanded-content--m-expanded__expanded-content-body--last-child--after--BorderBottomColor: var(--pf-global--BorderColor--100, #d2d2d2); - color: var(--pf-global--Color--100, #151515); - background-color: var(--pf-c-accordion--BackgroundColor); -} - -:host([bordered]) ::slotted(pf-accordion-header:first-child), -:host([large]) ::slotted(pf-accordion-header:first-child) { - display: block; - border-top: 1px solid var(--accordion__bordered--Color); - border-bottom: 1px solid var(--accordion__bordered--Color); -} - -:host([bordered]) ::slotted(pf-accordion-header:not(:first-child)), -:host([large]) ::slotted(pf-accordion-header:not(:first-child)) { - display: block; - border-bottom: 1px solid var(--accordion__bordered--Color); -} - -:host([bordered]) ::slotted(pf-accordion-header:is([expanded])), -:host([large]) ::slotted(pf-accordion-header:is([expanded])) { - display: block; - border-bottom: 0; -} - -:host([bordered]) ::slotted(pf-accordion-panel:is([expanded])), -:host([large]) ::slotted(pf-accordion-panel:is([expanded])) { - display: block; - border-bottom: 1px solid var(--accordion__bordered--Color); -} diff --git a/elements/pf-accordion/pf-accordion.ts b/elements/pf-accordion/pf-accordion.ts deleted file mode 100644 index 25c9b8bebc..0000000000 --- a/elements/pf-accordion/pf-accordion.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { observes } from '@patternfly/pfe-core/decorators/observes.js'; -import { listen } from '@patternfly/pfe-core/decorators/listen.js'; -import { property } from 'lit/decorators/property.js'; -import { customElement } from 'lit/decorators/custom-element.js'; - -import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; -import { NumberListConverter } from '@patternfly/pfe-core'; -import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; - -import { PfAccordionHeader, PfAccordionHeaderChangeEvent } from './pf-accordion-header.js'; -import { PfAccordionPanel } from './pf-accordion-panel.js'; - -export * from './pf-accordion-header.js'; -export * from './pf-accordion-panel.js'; - -import style from './pf-accordion.css'; - -export class PfAccordionExpandEvent extends Event { - constructor( - public toggle: PfAccordionHeader, - public panel: PfAccordionPanel, - ) { - super('expand', { bubbles: true, cancelable: true }); - } -} - -export class PfAccordionCollapseEvent extends Event { - constructor( - public toggle: PfAccordionHeader, - public panel: PfAccordionPanel, - ) { - super('collapse', { bubbles: true, cancelable: true }); - } -} - -/** - * An **accordion** is an interactive container that expands and collapses to hide or reveal nested content. It takes advantage of progressive disclosure to help reduce page scrolling, by allowing users to choose whether they want to show or hide more detailed information as needed. - * @summary Toggle the visibility of sections of content - * @alias Accordion - * @fires {AccordionExpandEvent} expand - when a panel expands - * @fires {AccordionCollapseEvent} collapse - when a panel collapses - */ -@customElement('pf-accordion') -export class PfAccordion extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - /** When true, only one accordion panel may be expanded at a time */ - @property({ reflect: true, type: Boolean }) single = false; - - /** Whether to apply the `bordered` style variant */ - @property({ type: Boolean, reflect: true }) bordered = false; - - /** Whether to apply the `large` style variant */ - @property({ type: Boolean, reflect: true }) large = false; - - @property({ type: Boolean, reflect: true }) fixed = false; - - /** - * Sets and reflects the currently expanded accordion 0-based indexes. - * Use commas to separate multiple indexes. - * ```html - * - * ... - * - * ``` - */ - @property({ attribute: 'expanded-index', converter: NumberListConverter }) - get expandedIndex(): number[] { - return this.#expandedIndex; - } - - set expandedIndex(value) { - const old = this.#expandedIndex; - this.#expandedIndex = value; - this.#tabindex.atFocusedItemIndex = value.at(-1) ?? -1; - if (JSON.stringify(old) !== JSON.stringify(value)) { - this.requestUpdate('expandedIndex', old); - this.collapseAll().then(async () => { - for (const i of this.expandedIndex) { - await this.expand(i); - } - }); - } - } - - #logger = new Logger(this); - - // actually is read in #init, by the `||=` operator - // eslint-disable-next-line no-unused-private-class-members - #initialized = false; - - #mo = new MutationObserver(() => this.#init()); - - #tabindex = RovingTabindexController.of(this, { - getItems: () => this.headers, - }); - - #expandedIndex: number[] = []; - - protected expandedSets: Set = new Set(); - - get #activeHeader() { - const { headers } = this; - const index = headers.findIndex(header => header.matches(':focus,:focus-within')); - return index > -1 ? headers.at(index) : undefined; - } - - get headers(): PfAccordionHeader[] { - return this.#allHeaders(); - } - - get panels(): PfAccordionPanel[] { - return this.#allPanels(); - } - - connectedCallback(): void { - super.connectedCallback(); - this.#mo.observe(this, { childList: true }); - this.#init(); - } - - render(): TemplateResult<1> { - return html` - - - `; - } - - async firstUpdated(): Promise { - let lastExpandedIndex: number; - const { headers, single } = this; - const lastExpanded = headers.filter(x => x.hasAttribute('expanded')).pop(); - if (lastExpanded) { - lastExpandedIndex = headers.indexOf(lastExpanded); - } - headers.forEach((header, index) => { - if (header.expanded && (!single || index === lastExpandedIndex)) { - this.#expandHeader(header, index); - const panel = this.#panelForHeader(header); - if (panel) { - this.#expandPanel(panel); - } - } - }); - } - - protected override async getUpdateComplete(): Promise { - const c = await super.getUpdateComplete(); - const results = await Promise.all([ - ...this.#allHeaders().map(x => x.updateComplete), - ...this.#allPanels().map(x => x.updateComplete), - ]); - return c && results.every(Boolean); - } - - @observes('large') - protected largeChanged(): void { - for (const el of [...this.headers, ...this.panels]) { - el.toggleAttribute('large', this.large); - } - } - - /** - * Initialize the accordion by connecting headers and panels - * with aria controls and labels; set up the default disclosure - * state if not set by the author; and check the URL for default - * open - */ - async #init() { - this.#initialized ||= !!await this.updateComplete; - this.updateAccessibility(); - } - - @listen('focusin') - protected updateActiveHeader(): void { - if (this.#activeHeader - && this.#activeHeader !== this.headers.at(this.#tabindex.atFocusedItemIndex)) { - this.#tabindex.atFocusedItemIndex = this.headers.indexOf(this.#activeHeader); - } - } - - #panelForHeader(header: PfAccordionHeader) { - const next = header.nextElementSibling; - if (!(next instanceof PfAccordionPanel)) { - return void this.#logger.error('Sibling element to a header needs to be a panel'); - } else { - return next; - } - } - - #expandHeader(header: PfAccordionHeader, index = this.#getIndex(header)) { - // If this index is not already listed in the expandedSets array, add it - this.expandedSets.add(index); - this.#expandedIndex = [...this.expandedSets as Set]; - header.expanded = true; - } - - #expandPanel(panel: PfAccordionPanel) { - panel.expanded = true; - panel.hidden = false; - } - - async #collapseHeader(header: PfAccordionHeader, index = this.#getIndex(header)) { - if (!this.expandedSets) { - await this.updateComplete; - } - this.expandedSets.delete(index); - header.expanded = false; - await header.updateComplete; - } - - async #collapsePanel(panel: PfAccordionPanel) { - await panel.updateComplete; - if (!panel.expanded) { - return; - } - - panel.expanded = false; - panel.hidden = true; - } - - @listen('change') - protected onChange(event: PfAccordionHeaderChangeEvent): void { - if (event instanceof PfAccordionHeaderChangeEvent && event.accordion === this) { - const index = this.#getIndex(event.target); - if (event.expanded) { - this.expand(index); - } else { - this.collapse(index); - } - event.stopPropagation(); - } - } - - #allHeaders(accordion: PfAccordion = this): PfAccordionHeader[] { - return Array.from(accordion.children ?? []).filter((x): x is PfAccordionHeader => - x instanceof PfAccordionHeader); - } - - #allPanels(accordion: PfAccordion = this): PfAccordionPanel[] { - return Array.from(accordion.children ?? []).filter((x): x is PfAccordionPanel => - x instanceof PfAccordionPanel); - } - - #getIndex(el: Element | null) { - if (el instanceof PfAccordionHeader) { - return this.headers.findIndex(header => header.id === el.id); - } - - if (el instanceof PfAccordionPanel) { - return this.panels.findIndex(panel => panel.id === el.id); - } - - this.#logger.warn('The #getIndex method expects to receive a header or panel element.'); - return -1; - } - - public updateAccessibility(): void { - const { headers } = this; - - // For each header in the accordion, attach the aria connections - headers.forEach(header => { - const panel = this.#panelForHeader(header); - if (panel) { - header.setAttribute('aria-controls', panel.id); - panel.setAttribute('aria-labelledby', header.id); - panel.hidden = !panel.expanded; - } - }); - } - - /** - * Accepts a 0-based index value (integer) for the set of accordion items to expand. - * Accepts an optional parent accordion to search for headers and panels. - * @param index index (0-based) of the panel to expand - */ - public async expand(index: number): Promise { - if (index === -1) { - return; - } - - // Get all the headers and capture the item by index value - if (this.single) { - await Promise.all([ - ...this.headers.map((header, index) => header.expanded && this.collapse(index)), - ]); - } - - const header = this.headers[index]; - if (!header) { - return; - } - - const panel = this.#panelForHeader(header); - if (!panel) { - return; - } - - // If the header and panel exist, open both - this.#expandHeader(header, index); - this.#expandPanel(panel); - - this.dispatchEvent(new PfAccordionExpandEvent(header, panel)); - - await this.updateComplete; - } - - /** - * Accepts a 0-based index value (integer) for the set of accordion items to collapse. - * @param index index (0-based) of the panel to collapse - */ - public async collapse(index: number): Promise { - const header = this.headers.at(index); - const panel = this.panels.at(index); - - if (!header || !panel) { - return; - } - - this.#collapseHeader(header); - this.#collapsePanel(panel); - - this.dispatchEvent(new PfAccordionCollapseEvent(header, panel)); - await this.updateComplete; - } - - /** - * Accepts a 0-based index value (integer) for the set of accordion items to expand or collapse. - * @param index index (0-based) of the panel to toggle - */ - public async toggle(index: number): Promise { - const { headers } = this; - const header = headers[index]; - - if (!header.expanded) { - await this.expand(index); - } else { - await this.collapse(index); - } - } - - /** - * Expands all accordion items. - */ - public async expandAll(): Promise { - this.headers.forEach(header => this.#expandHeader(header)); - this.panels.forEach(panel => this.#expandPanel(panel)); - await this.updateComplete; - } - - - /** - * Collapses all accordion items. - */ - public async collapseAll(): Promise { - this.headers.forEach(header => this.#collapseHeader(header)); - this.panels.forEach(panel => this.#collapsePanel(panel)); - await this.updateComplete; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-accordion': PfAccordion; - } -} diff --git a/elements/pf-alert/README.md b/elements/pf-alert/README.md deleted file mode 100644 index 19d47e3dfb..0000000000 --- a/elements/pf-alert/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# pf-alert - -The `pf-alert` web component displays PatternFly-styled alerts. It can be used inline in pages or as a toast notification (if a static helper is provided separately). Alerts support several visual **variants** (for example: `info`, `success`, `warning`, `danger`), an optional title slot, body content, and an **action links** slot for interactive controls. Alerts can also be **closable** and **expandable**. - -## Installation - -Import the element in your page or application as an ES module: - -```html - -``` - -## Basic usage - -Inline alert example: - -```html - - Operation Success - - The operation completed successfully. - - - - System Update - - A new system update is available. - -
- Update Now - Later -
-
-``` - - diff --git a/elements/pf-alert/demo/custom-icons.html b/elements/pf-alert/demo/custom-icons.html deleted file mode 100644 index 856ef7bc6c..0000000000 --- a/elements/pf-alert/demo/custom-icons.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - diff --git a/elements/pf-alert/demo/expandable.html b/elements/pf-alert/demo/expandable.html deleted file mode 100644 index f53b7ce423..0000000000 --- a/elements/pf-alert/demo/expandable.html +++ /dev/null @@ -1,27 +0,0 @@ - -

Success alert description. This should tell the user more information about the alert.

-
- - -

Success alert description. This should tell the user more information about the alert.

- View details - Ignore -
- - - - diff --git a/elements/pf-alert/demo/index.html b/elements/pf-alert/demo/index.html deleted file mode 100644 index 3c3ae5e2eb..0000000000 --- a/elements/pf-alert/demo/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/elements/pf-alert/demo/inline.html b/elements/pf-alert/demo/inline.html deleted file mode 100644 index 9798050f4b..0000000000 --- a/elements/pf-alert/demo/inline.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - diff --git a/elements/pf-alert/demo/plain.html b/elements/pf-alert/demo/plain.html deleted file mode 100644 index 6510c7c6be..0000000000 --- a/elements/pf-alert/demo/plain.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - diff --git a/elements/pf-alert/demo/title-slot.html b/elements/pf-alert/demo/title-slot.html deleted file mode 100644 index d1464f1c53..0000000000 --- a/elements/pf-alert/demo/title-slot.html +++ /dev/null @@ -1,29 +0,0 @@ - -

Custom alert title

-
- - -

Info alert title

-
- - -

Success alert title

-
- - -

Warning alert title

-
- - -

Danger alert title

-
- - - - diff --git a/elements/pf-alert/demo/variations.html b/elements/pf-alert/demo/variations.html deleted file mode 100644 index 1baf997dc6..0000000000 --- a/elements/pf-alert/demo/variations.html +++ /dev/null @@ -1,37 +0,0 @@ - - Success alert description. This should tell the user more information about the alert. - View details - Ignore - - - - - Success alert description. - This should tell the user more information about the alert. - This is a link. - - - - Short alert description. - - - - Short alert description. - - - - - diff --git a/elements/pf-alert/docs/pf-alert.md b/elements/pf-alert/docs/pf-alert.md deleted file mode 100644 index db0de61f04..0000000000 --- a/elements/pf-alert/docs/pf-alert.md +++ /dev/null @@ -1,17 +0,0 @@ -{% renderOverview %} - -{% endrenderOverview %} - -{% band header="Usage" %}{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-alert/pf-alert.css b/elements/pf-alert/pf-alert.css deleted file mode 100644 index e6cb4bbfb5..0000000000 --- a/elements/pf-alert/pf-alert.css +++ /dev/null @@ -1,193 +0,0 @@ -[hidden] { - display: none !important; -} - -:host { - --pf-c-alert--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); - --pf-c-alert--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - --pf-c-alert--GridTemplateColumns: max-content 1fr max-content; - --pf-c-alert--GridTemplateAreas: "icon title close" ". description description" ". action action"; - --pf-c-alert--BorderTopWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-alert--BorderTopColor: var(--pf-global--default-color--200, #009596); - --pf-c-alert--PaddingTop: var(--pf-global--spacer--md, 1rem); - --pf-c-alert--PaddingRight: var(--pf-global--spacer--md, 1rem); - --pf-c-alert--PaddingBottom: var(--pf-global--spacer--md, 1rem); - --pf-c-alert--PaddingLeft: var(--pf-global--spacer--md, 1rem); - --pf-c-alert__FontSize: var(--pf-global--FontSize--sm, 0.875rem); - --pf-c-alert__toggle--MarginTop: calc(-1 * var(--pf-global--spacer--form-element, 0.375rem) - 0.0625rem); - --pf-c-alert__toggle--MarginBottom: calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)); - --pf-c-alert__toggle--MarginLeft: calc(-1 * var(--pf-global--spacer--md, 1rem)); - --pf-c-alert__toggle-icon--Rotate: 0; - --pf-c-alert__toggle-icon--Transition: var(--pf-global--Transition, all 250ms cubic-bezier(0.42, 0, 0.58, 1)); - --pf-c-alert__icon--Color: var(--pf-global--default-color--200, #009596); - --pf-c-alert__icon--MarginTop: 0.0625rem; - --pf-c-alert__icon--MarginRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-alert__icon--FontSize: var(--pf-global--icon--FontSize--md, 1.125rem); - --pf-c-alert__title--FontWeight: var(--pf-global--FontWeight--bold, 700); - --pf-c-alert__title--Color: var(--pf-global--default-color--300, #003737); - --pf-c-alert__title--max-lines: 1; - --pf-c-alert__action--MarginTop: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); - --pf-c-alert__action--MarginBottom: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); - --pf-c-alert__action--TranslateY: 0.125rem; - --pf-c-alert__action--MarginRight: calc(var(--pf-global--spacer--sm, 0.5rem) * -1); - --pf-c-alert__description--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-alert__action-group--PaddingTop-base: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-alert__action-group--PaddingTop: var(--pf-c-alert__action-group--PaddingTop-base); - --pf-c-alert__description--action-group--PaddingTop-base: var(--pf-global--spacer--md, 1rem); - --pf-c-alert__description--action-group--PaddingTop: var(--pf-c-alert__description--action-group--PaddingTop-base); - --pf-c-alert__action-group__c-button--not-last-child--MarginRight: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-alert--m-success--BorderTopColor: var(--pf-global--success-color--100, #3e8635); - --pf-c-alert--m-success__icon--Color: var(--pf-global--success-color--100, #3e8635); - --pf-c-alert--m-success__title--Color: var(--pf-global--success-color--200, #1e4f18); - --pf-c-alert--m-danger--BorderTopColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-alert--m-danger__icon--Color: var(--pf-global--danger-color--100, #c9190b); - --pf-c-alert--m-danger__title--Color: var(--pf-global--danger-color--200, #a30000); - --pf-c-alert--m-warning--BorderTopColor: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-alert--m-warning__icon--Color: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-alert--m-warning__title--Color: var(--pf-global--warning-color--200, #795600); - --pf-c-alert--m-info--BorderTopColor: var(--pf-global--info-color--100, #2b9af3); - --pf-c-alert--m-info__icon--Color: var(--pf-global--info-color--100, #2b9af3); - --pf-c-alert--m-info__title--Color: var(--pf-global--info-color--200, #002952); - --pf-c-alert--m-inline--BoxShadow: none; - --pf-c-alert--m-inline--BackgroundColor: var(--pf-global--palette--cyan-50, #f2f9f9); - --pf-c-alert--m-inline--m-success--BackgroundColor: var(--pf-global--palette--green-50, #f3faf2); - --pf-c-alert--m-inline--m-danger--BackgroundColor: var(--pf-global--palette--red-50, #faeae8); - --pf-c-alert--m-inline--m-warning--BackgroundColor: var(--pf-global--palette--gold-50, #fdf7e7); - --pf-c-alert--m-inline--m-info--BackgroundColor: var(--pf-global--palette--blue-50, #e7f1fa); - --pf-c-alert--m-inline--m-plain--BorderTopWidth: 0; - --pf-c-alert--m-inline--m-plain--BackgroundColor: transparent; - --pf-c-alert--m-inline--m-plain--PaddingTop: 0; - --pf-c-alert--m-inline--m-plain--PaddingRight: 0; - --pf-c-alert--m-inline--m-plain--PaddingBottom: 0; - --pf-c-alert--m-inline--m-plain--PaddingLeft: 0; - --pf-c-alert--m-expandable--GridTemplateColumns: auto max-content 1fr max-content; - --pf-c-alert--m-expandable--GridTemplateAreas: "toggle icon title close" ". . description description" ". . action action"; - --pf-c-alert--m-expandable__description--action-group--PaddingTop: var(--pf-c-alert__action-group--PaddingTop-base); - --pf-c-alert--m-expanded__toggle-icon--Rotate: 90deg; - --pf-c-alert--m-expanded__description--action-group--PaddingTop: var(--pf-c-alert__description--action-group--PaddingTop-base); - color: var(--pf-global--Color--100, #151515); - position: relative; - display: grid; - padding: var(--pf-c-alert--PaddingTop) var(--pf-c-alert--PaddingRight) var(--pf-c-alert--PaddingBottom) var(--pf-c-alert--PaddingLeft); - font-size: var(--pf-c-alert__FontSize); - background-color: var(--pf-c-alert--BackgroundColor); - border-top: var(--pf-c-alert--BorderTopWidth) solid var(--pf-c-alert--BorderTopColor); - box-shadow: var(--pf-c-alert--BoxShadow); - grid-template-columns: var(--pf-c-alert--GridTemplateColumns); - grid-template-areas: var(--pf-c-alert--GridTemplateAreas); -} - -:host([variant="success"]) { - --pf-c-alert--BorderTopColor: var(--pf-c-alert--m-success--BorderTopColor); - --pf-c-alert__icon--Color: var(--pf-c-alert--m-success__icon--Color); - --pf-c-alert__title--Color: var(--pf-c-alert--m-success__title--Color); - --pf-c-alert--m-inline--BackgroundColor: var(--pf-c-alert--m-inline--m-success--BackgroundColor); -} - -:host([variant="danger"]) { - --pf-c-alert--BorderTopColor: var(--pf-c-alert--m-danger--BorderTopColor); - --pf-c-alert__icon--Color: var(--pf-c-alert--m-danger__icon--Color); - --pf-c-alert__title--Color: var(--pf-c-alert--m-danger__title--Color); - --pf-c-alert--m-inline--BackgroundColor: var(--pf-c-alert--m-inline--m-danger--BackgroundColor); -} - -:host([variant="warning"]) { - --pf-c-alert--BorderTopColor: var(--pf-c-alert--m-warning--BorderTopColor); - --pf-c-alert__icon--Color: var(--pf-c-alert--m-warning__icon--Color); - --pf-c-alert__title--Color: var(--pf-c-alert--m-warning__title--Color); - --pf-c-alert--m-inline--BackgroundColor: var(--pf-c-alert--m-inline--m-warning--BackgroundColor); -} - -:host([variant="info"]) { - --pf-c-alert--BorderTopColor: var(--pf-c-alert--m-info--BorderTopColor); - --pf-c-alert__icon--Color: var(--pf-c-alert--m-info__icon--Color); - --pf-c-alert__title--Color: var(--pf-c-alert--m-info__title--Color); - --pf-c-alert--m-inline--BackgroundColor: var(--pf-c-alert--m-inline--m-info--BackgroundColor); -} - -:host([inline]) { - --pf-c-alert--BoxShadow: var(--pf-c-alert--m-inline--BoxShadow); - --pf-c-alert--BackgroundColor: var(--pf-c-alert--m-inline--BackgroundColor); -} - -:host([plain]) { - --pf-c-alert--BorderTopWidth: var(--pf-c-alert--m-inline--m-plain--BorderTopWidth); - --pf-c-alert--BackgroundColor: var(--pf-c-alert--m-inline--m-plain--BackgroundColor); - --pf-c-alert--PaddingTop: var(--pf-c-alert--m-inline--m-plain--PaddingTop); - --pf-c-alert--PaddingRight: var(--pf-c-alert--m-inline--m-plain--PaddingRight); - --pf-c-alert--PaddingBottom: var(--pf-c-alert--m-inline--m-plain--PaddingBottom); - --pf-c-alert--PaddingLeft: var(--pf-c-alert--m-inline--m-plain--PaddingLeft); -} - -:host([expandable]) { - --pf-c-alert--GridTemplateColumns: var(--pf-c-alert--m-expandable--GridTemplateColumns); - --pf-c-alert--GridTemplateAreas: var(--pf-c-alert--m-expandable--GridTemplateAreas); - --pf-c-alert__description--action-group--PaddingTop: var(--pf-c-alert--m-expandable__description--action-group--PaddingTop); -} - -:host([expanded]) { - --pf-c-alert__toggle-icon--Rotate: var(--pf-c-alert--m-expanded__toggle-icon--Rotate); - --pf-c-alert__description--action-group--PaddingTop: var(--pf-c-alert--m-expanded__description--action-group--PaddingTop); -} - -#toggle { - margin-top: var(--pf-c-alert__toggle--MarginTop); - margin-bottom: var(--pf-c-alert__toggle--MarginBottom); - margin-left: var(--pf-c-alert__toggle--MarginLeft); -} - -#icon { - grid-area: icon; - display: flex; - margin-top: var(--pf-c-alert__icon--MarginTop); - margin-right: var(--pf-c-alert__icon--MarginRight); - font-size: var(--pf-c-alert__icon--FontSize); - --pf-icon--size: var(--pf-c-alert__icon--FontSize); - color: var(--pf-c-alert__icon--Color); - pf-icon, - ::slotted(pf-icon) { - translate: 0 0.125em; - } -} - -#title { - grid-area: title; - font-weight: var(--pf-c-alert__title--FontWeight); - color: var(--pf-c-alert__title--Color); - word-break: break-word; - ::slotted(*) { - color: inherit; - font-weight: inherit; - } - :is(h1,h2,h3,h4,h5,h6), - ::slotted(:is(h1,h2,h3,h4,h5,h6)) { - margin-block: 0 !important; - } -} - -#close { - grid-area: close; -} - -#description { - grid-area: description; - padding-top: var(--pf-c-alert__description--PaddingTop); - word-break: break-word; -} - -#actions { - grid-area: action; - --pf-c-alert__action-group--PaddingTop: var(--pf-c-alert__description--action-group--PaddingTop); - - & ::slotted(a) { - text-decoration: none; - color: #06c; - margin-inline-end: 1rem; - } - - & ::slotted(a:hover), - & ::slotted(a:focus), - & ::slotted(a:active) { - color: #004080; - } -} diff --git a/elements/pf-alert/pf-alert.ts b/elements/pf-alert/pf-alert.ts deleted file mode 100644 index cef2c34d99..0000000000 --- a/elements/pf-alert/pf-alert.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; - -import { observes } from '@patternfly/pfe-core/decorators.js'; - -import '@patternfly/elements/pf-icon/pf-icon.js'; -import '@patternfly/elements/pf-button/pf-button.js'; - -import styles from './pf-alert.css'; - -const VariantIconMap = new Map(Object.entries({ - info: 'info-circle', - success: 'check-circle', - warning: 'exclamation-triangle', - danger: 'exclamation-circle', - neutral: 'bell', -})); - -export class PfAlertCloseEvent extends Event { - constructor(public reason: 'closed' | 'timeout' = 'closed') { - super('close', { bubbles: true, cancelable: true }); - } -} - -/** - * An **alert** is a notification that provides brief information to the user - * without blocking their workflow. - * - * @fires close - When an alert is closed e.g. when close button is clicked or when the alert times - * out. Cancel the event to prevent the alert from being removed. - */ -@customElement('pf-alert') -export class PfAlert extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** - * Use the `timeout` property to automatically dismiss an alert after a period - * of time. If set to `true`, the timeout will be 8000 milliseconds. Provide a - * specific value to dismiss the alert after a different number of - * milliseconds. - */ - @property({ type: Number }) timeout: number | true = 0; - - /** - * PatternFly supports several alert variants for different scenarios. - * Each variant has an associated status icon, background, and alert title - * coded to communicate the severity of an alert. Use the variant property to - * apply the following styling options. If no variant is specified, then the - * variant will be set to "default". - * - * - **Default** - Use for generic messages with no associated severity - * - **Info** - Use for general informational messages - * - **Success** - Use to indicate that a task or process has been completed successfully - * - **Warning** - Use to indicate that a non-critical error has occurred - * - **Danger** - Use to indicate that a critical or blocking error has occurred - */ - @property({ reflect: true }) - variant: - | 'warning' - | 'custom' - | 'neutral' - | 'info' - | 'success' - | 'danger' = 'neutral'; - - /** - * Use the `icon` attribute to replace a default alert icon with a custom icon. - * The `icon` attribute is overridden by the `icon` slot. - */ - @property() icon?: string; - - /** - * The title of the alert. Overridden by the title slot. - */ - @property({ attribute: 'title-text', reflect: true }) titleText?: string; - - /** - * The heading level of the alert's title. Overridden by the title slot. - * Default: 3 - */ - @property({ attribute: 'title-level', reflect: true }) titleLevel?: 1 | 2 | 3 | 4 | 5 | 6; - - /** - * Use inline alerts to display an alert inline with content. All alert - * variants may use the `inline` attribute to position alerts in content-heavy - * areas, such as within forms, wizards, or drawers. - */ - @property({ type: Boolean, reflect: true }) inline = false; - - /** - * Use the `plain` attribute to make any inline alert plain. Plain styling - * removes the colored background but keeps colored text and icons. - */ - @property({ type: Boolean, reflect: true }) plain = false; - - /** - * An alert can contain additional, hidden information that is made visible - * when users click a caret icon. This information can be expanded and - * collapsed each time the icon is clicked. - * - * It is not recommended to use an expandable alert with a timeout in a toast - * alert group because the alert could timeout before users have time to - * interact with and view the entire alert. - * - * See the toast alert considerations section of the alert accessibility - * documentation to understand the accessibility risks associated with using - * toast alerts. - */ - @property({ reflect: true, type: Boolean }) expandable = false; - - /** - * True when an expandable alert is expanded - */ - @property({ reflect: true, type: Boolean }) expanded = false; - - /** - * When true, the alert displays a close button - * Clicking the close button removes the alert - */ - @property({ reflect: true, type: Boolean }) dismissable = false; - - #timeoutId?: number; - - override disconnectedCallback(): void { - super.disconnectedCallback(); - clearTimeout(this.#timeoutId); - } - - override render(): TemplateResult<1> { - const { expandable, expanded, variant } = this; - const icon = this.icon ?? VariantIconMap.get(variant); - return html` - - - -
- - - -
- -
- ${this.#renderDefaultTitle()} -
- -
- -
- -
- -
- - - - `; - } - - @observes('timeout') - protected timeoutChanged(): void { - clearTimeout(this.#timeoutId); - let { timeout } = this; - if (timeout === true) { - timeout = 8000; - } - if (timeout > 0) { - this.#timeoutId = setTimeout(() => { - if (this.isConnected && this.dispatchEvent(new PfAlertCloseEvent('timeout'))) { - this.remove(); - } - }, timeout) as unknown as number; - } - } - - #renderDefaultTitle() { - if (!this.titleText) { - return ''; - } - switch (this.titleLevel ?? 3) { - case 1: return html`

${this.titleText}

`; - case 2: return html`

${this.titleText}

`; - case 4: return html`

${this.titleText}

`; - case 5: return html`
${this.titleText}
`; - case 6: return html`
${this.titleText}
`; - case 3: - default: return html`

${this.titleText}

`; - } - } - - #onCloseClick() { - if (this.isConnected && this.dispatchEvent(new PfAlertCloseEvent())) { - clearTimeout(this.#timeoutId); - this.remove(); - } - } - - #onToggleClick() { - this.expanded = !this.expanded; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-alert': PfAlert; - } -} diff --git a/elements/pf-alert/test/pf-alert.e2e.ts b/elements/pf-alert/test/pf-alert.e2e.ts deleted file mode 100644 index d89e81d7b4..0000000000 --- a/elements/pf-alert/test/pf-alert.e2e.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('pf-alert E2E Tests', () => { - test.beforeEach(async ({ page }) => { - await page.setContent(` - - Alert Title - Alert description - - `); - }); - - test('should render title and description', async ({ page }) => { - const title = page.locator('pf-alert >>> #title-area slot[name="title"]'); - const desc = page.locator('pf-alert >>> #description slot'); - await expect(title).toHaveText('Alert Title'); - await expect(desc).toHaveText('Alert description'); - }); - - test('close button removes alert', async ({ page }) => { - await page.locator('pf-alert >>> #close-button').click(); - const alert = page.locator('pf-alert'); - await expect(alert).toHaveCount(0); - }); - - test('toggle button expands/collapses content', async ({ page }) => { - await page.setContent(` - - Extra content - - `); - const toggle = page.locator('pf-alert >>> #toggle-button'); - await toggle.click(); - const expandedContent = page.locator('pf-alert >>> #expandable-description'); - await expect(expandedContent).toBeVisible(); - }); - - test('keyboard navigation works', async ({ page }) => { - const btn = page.locator('pf-alert >>> #close-button'); - await btn.focus(); - await page.keyboard.press('Enter'); - const alert = page.locator('pf-alert'); - await expect(alert).toHaveCount(0); - }); - - test('accessibility checks', async ({ page }) => { - const container = page.locator('pf-alert >>> #container'); - await expect(container).toHaveAttribute('role', 'alert'); // אם הוספת role - }); -}); diff --git a/elements/pf-alert/test/pf-alert.spec.ts b/elements/pf-alert/test/pf-alert.spec.ts deleted file mode 100644 index 2c5a005ca6..0000000000 --- a/elements/pf-alert/test/pf-alert.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { html, fixture, expect } from '@open-wc/testing'; -import '../pf-alert.js'; - -describe('pf-alert Unit Tests', () => { - it('should create the component', async () => { - const el = await fixture(html``) as any; - expect(el).to.exist; - expect(el.variant).to.equal('neutral'); - }); - - it('should render a title slot', async () => { - const el = await fixture(html` - My Title - `); - const titleSlot = el.shadowRoot!.querySelector('#title slot[name="title"]'); - expect(titleSlot).to.exist; - }); - - it('close button should appear when dismissable=true', async () => { - const el = await fixture(html``); - const btn = el.shadowRoot!.querySelector('#close')!; - expect(btn.hasAttribute('hidden')).to.be.false; - }); - - it('should remove itself after timeout', async () => { - const el = await fixture(html``); - const removed = new Promise(resolve => el.addEventListener('close', () => resolve())); - await removed; - expect(document.body.contains(el)).to.be.false; - }); - - it('should toggle expanded when toggle button clicked', async () => { - const el = await fixture(html` - Content - `) as any; - await el.updateComplete; - const toggle = el.shadowRoot!.querySelector('#toggle') as HTMLElement; - const initial = el.expanded; - toggle.click(); - await el.updateComplete; - expect(el.expanded).to.equal(!initial); - toggle.click(); - await el.updateComplete; - expect(el.expanded).to.equal(initial); - }); -}); diff --git a/elements/pf-avatar/README.md b/elements/pf-avatar/README.md deleted file mode 100644 index ddad11128f..0000000000 --- a/elements/pf-avatar/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# PatternFly Elements Avatar - -`` is an element for displaying a user's avatar image. If the user in -question has provided a custom avatar, provide it and it will be displayed. - -Read more about Avatar in the [PatternFly Elements Avatar documentation](https://patternflyelements.org/components/avatar) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-avatar/pf-avatar.js'; -``` -## Usage - -```html - -``` diff --git a/elements/pf-avatar/demo/bordered.html b/elements/pf-avatar/demo/bordered.html deleted file mode 100644 index 3fa3537d49..0000000000 --- a/elements/pf-avatar/demo/bordered.html +++ /dev/null @@ -1,50 +0,0 @@ -
-
-
Small
-
- -
-
- -
-
Medium
-
- -
-
- -
-
Large
-
- -
-
- -
-
Extra Large
-
- -
-
-
- - - - - diff --git a/elements/pf-avatar/demo/dark-border.html b/elements/pf-avatar/demo/dark-border.html deleted file mode 100644 index 1303f72925..0000000000 --- a/elements/pf-avatar/demo/dark-border.html +++ /dev/null @@ -1,49 +0,0 @@ -
-
-
Small
-
- -
-
- -
-
Medium
-
- -
-
- -
-
Large
-
- -
-
- -
-
Extra Large
-
- -
-
-
- - - - diff --git a/elements/pf-avatar/demo/index.html b/elements/pf-avatar/demo/index.html deleted file mode 100644 index bb29db1674..0000000000 --- a/elements/pf-avatar/demo/index.html +++ /dev/null @@ -1,13 +0,0 @@ -
- -
- - - - diff --git a/elements/pf-avatar/demo/sizes.html b/elements/pf-avatar/demo/sizes.html deleted file mode 100644 index c4ba4bde5c..0000000000 --- a/elements/pf-avatar/demo/sizes.html +++ /dev/null @@ -1,49 +0,0 @@ -
-
-
Small
-
- -
-
- -
-
Medium
-
- -
-
- -
-
Large
-
- -
-
- -
-
Extra Large
-
- -
-
-
- - - - diff --git a/elements/pf-avatar/docs/pf-avatar.md b/elements/pf-avatar/docs/pf-avatar.md deleted file mode 100644 index 29346cbd58..0000000000 --- a/elements/pf-avatar/docs/pf-avatar.md +++ /dev/null @@ -1,28 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - -{% endrenderOverview %} - -{% band header="Usage" %} - {% htmlexample style="display: flex; gap: 1em;" %} - - - - - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-avatar/pf-avatar.css b/elements/pf-avatar/pf-avatar.css deleted file mode 100644 index eb7b6aba00..0000000000 --- a/elements/pf-avatar/pf-avatar.css +++ /dev/null @@ -1,80 +0,0 @@ -:host { - display: inline-block; - /** Border color for avatar */ - --pf-c-avatar--BorderColor: transparent; - /** Border width for avatar */ - --pf-c-avatar--BorderWidth: 0; - /** Border radius for avatar */ - --pf-c-avatar--BorderRadius: var(--pf-global--BorderRadius--lg, 30em); - /** Width for avatar */ - --pf-c-avatar--Width: 2.25rem; - /** Height for avatar */ - --pf-c-avatar--Height: 2.25rem; - /** Width for small avatar */ - --pf-c-avatar--m-sm--Width: 1.5rem; - /** Height for small avatar */ - --pf-c-avatar--m-sm--Height: 1.5rem; - /** Width for medium avatar */ - --pf-c-avatar--m-md--Width: 2.25rem; - /** Height for medium avatar */ - --pf-c-avatar--m-md--Height: 2.25rem; - /** Width for large avatar */ - --pf-c-avatar--m-lg--Width: 4.5rem; - /** Height for large avatar */ - --pf-c-avatar--m-lg--Height: 4.5rem; - /** Width for extra large avatar */ - --pf-c-avatar--m-xl--Width: 8rem; - /** Height for extra large avatar */ - --pf-c-avatar--m-xl--Height: 8rem; - --pf-c-avatar--m-light--BorderColor: var(--pf-global--BorderColor--dark-100, #d2d2d2); - --pf-c-avatar--m-light--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - /** Border color for dark avatar */ - --pf-c-avatar--m-dark--BorderColor: var(--pf-global--palette--black-700, #4f5255); - --pf-c-avatar--m-dark--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - width: var(--pf-c-avatar--Width); - height: var(--pf-c-avatar--Height); - border-radius: var(--pf-c-avatar--BorderRadius); -} - -:host([hidden]), -[hidden] { - display: none !important; -} - -svg, -img { - display: inline; - object-fit: cover; - width: var(--pf-c-avatar--Width); - height: var(--pf-c-avatar--Height); - border-radius: var(--pf-c-avatar--BorderRadius); - border: var(--pf-c-avatar--BorderWidth) solid var(--pf-c-avatar--BorderColor); -} - -:host([border]) { - --pf-c-avatar--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); -} - -:host([border="dark"]) { - --pf-c-avatar--BorderColor: var(--pf-c-avatar--m-dark--BorderColor); -} - -:host([size="sm"]) { - --pf-c-avatar--Width: var(--pf-c-avatar--m-sm--Width); - --pf-c-avatar--Height: var(--pf-c-avatar--m-sm--Height); -} - -:host([size="md"]) { - --pf-c-avatar--Width: var(--pf-c-avatar--m-md--Width); - --pf-c-avatar--Height: var(--pf-c-avatar--m-md--Height); -} - -:host([size="lg"]) { - --pf-c-avatar--Width: var(--pf-c-avatar--m-lg--Width); - --pf-c-avatar--Height: var(--pf-c-avatar--m-lg--Height); -} - -:host([size="xl"]) { - --pf-c-avatar--Width: var(--pf-c-avatar--m-xl--Width); - --pf-c-avatar--Height: var(--pf-c-avatar--m-xl--Height); -} diff --git a/elements/pf-avatar/pf-avatar.ts b/elements/pf-avatar/pf-avatar.ts deleted file mode 100644 index 8e84c207af..0000000000 --- a/elements/pf-avatar/pf-avatar.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { property } from 'lit/decorators/property.js'; -import { customElement } from 'lit/decorators/custom-element.js'; - -import style from './pf-avatar.css'; - -export class PfAvatarLoadEvent extends Event { - constructor(public originalEvent: Event) { - super('load', { bubbles: true }); - } -} - -/** - * An **avatar** is a visual used to represent a user. It may contain an image or a placeholder graphic. - * @summary For displaying a user's avatar image - * @alias Avatar - * @fires {PfAvatarLoadEvent} load - when the avatar loads - */ -@customElement('pf-avatar') -export class PfAvatar extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - /** The URL to the user's custom avatar image. */ - @property() src?: string; - - /** The alt text for the avatar image. */ - @property({ reflect: true }) alt?: string = 'Avatar image'; - - /** Size of the Avatar */ - @property({ reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'sm'; - - /** Whether to display a border around the avatar */ - @property({ reflect: true }) border?: 'light' | 'dark'; - - /** Whether or not the Avatar image is dark */ - @property({ type: Boolean, reflect: true }) dark = false; - - render(): TemplateResult<1> { - return this.src != null ? html` - ${this.alt ?? ''} - ` : this.dark ? html` - - - - - ` : html` - - - - - - `; - } - - #onLoad(event: Event) { - this.dispatchEvent(new PfAvatarLoadEvent(event)); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-avatar': PfAvatar; - } -} diff --git a/elements/pf-avatar/test/pf-avatar.spec.ts b/elements/pf-avatar/test/pf-avatar.spec.ts deleted file mode 100644 index 99fdae3ee3..0000000000 --- a/elements/pf-avatar/test/pf-avatar.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { html, expect, oneEvent, nextFrame } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfAvatar, PfAvatarLoadEvent } from '@patternfly/elements/pf-avatar/pf-avatar.js'; - -describe('', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-avatar')).to.be.an.instanceof(PfAvatar); - }); - - it('should upgrade', async function() { - const el = await createFixture(html``); - expect(el, 'pf-badge should be an instance of PfAvatar') - .to.be.an.instanceOf(customElements.get('pf-avatar')) - .and - .to.be.an.instanceOf(PfAvatar); - }); - - describe('without src attr', function() { - let element: PfAvatar; - beforeEach(async function() { - element = await createFixture(html``); - await nextFrame(); - }); - it('loads default avatar', function() { - const { offsetWidth } = element; - expect(offsetWidth).to.be.greaterThan(0); - }); - }); - - describe('with a src attr', function() { - let element: PfAvatar; - let loaded: string | undefined; - const datauri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAB0UlEQVR4Xu3UAQ0AAAyDsM+/6QspcwAh2zXawGj64K8A8AgKoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B6gAHADOH4PUAC4ARy/BygA3ACO3wMUAG4Ax+8BCgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7gALADeD4PUAB4AZw/B6gAHADOH4PUAC4ARy/BygA3ACO3wMUAG4Ax+8BCgA3gOP3AAWAG8Dxe4ACwA3g+D1AAeAGcPweoABwAzh+D1AAuAEcvwcoANwAjt8DFABuAMfvAQoAN4Dj9wAFgBvA8XuAAsAN4Pg9QAHgBnD8HqAAcAM4fg9QALgBHL8HKADcAI7fAxQAbgDH7wEKADeA4/cABYAbwPF7ADyAB6SPAIFm19U7AAAAAElFTkSuQmCC'; - const onLoad = (e: PfAvatarLoadEvent) => { - const paths = e.originalEvent.composedPath() as HTMLImageElement[]; - loaded = paths.find(x => x.localName === 'img')?.src; - }; - beforeEach(async function() { - element = await createFixture(html``); - setTimeout(() => element.src = datauri); - await oneEvent(element, 'load'); - }); - it('loads the image', function() { - expect(loaded).to.equal(datauri); - }); - }); -}); diff --git a/elements/pf-back-to-top/README.md b/elements/pf-back-to-top/README.md deleted file mode 100644 index 5e4dfb99ff..0000000000 --- a/elements/pf-back-to-top/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Back to top - -The **back to top** component is a shortcut that allows users to quickly navigate to the top of a lengthy content page. - - -## Installation -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-back-to-top/pf-back-to-top.js'; -``` - -## Usage - -```html -Back to Top - -``` - -[docs]: https://patternflyelements.org/components/back-to-top diff --git a/elements/pf-back-to-top/demo/always-visible.html b/elements/pf-back-to-top/demo/always-visible.html deleted file mode 100644 index a607050b23..0000000000 --- a/elements/pf-back-to-top/demo/always-visible.html +++ /dev/null @@ -1,28 +0,0 @@ -
-

Always visible

- Focusable element (top) -
- -Back to top - - - - diff --git a/elements/pf-back-to-top/demo/button-no-text.html b/elements/pf-back-to-top/demo/button-no-text.html deleted file mode 100644 index 0f6de199a2..0000000000 --- a/elements/pf-back-to-top/demo/button-no-text.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-

Button No Text

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-back-to-top/demo/button.html b/elements/pf-back-to-top/demo/button.html deleted file mode 100644 index 3a2806507f..0000000000 --- a/elements/pf-back-to-top/demo/button.html +++ /dev/null @@ -1,52 +0,0 @@ - - - Accessibility Warning Using the Button/JS variant, implementation must apply click event and focus to the element that is scrolled to. - -
-
-

Button

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-back-to-top/demo/index.html b/elements/pf-back-to-top/demo/index.html deleted file mode 100644 index 521bb16603..0000000000 --- a/elements/pf-back-to-top/demo/index.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-back-to-top/demo/label.html b/elements/pf-back-to-top/demo/label.html deleted file mode 100644 index 0fa9c01840..0000000000 --- a/elements/pf-back-to-top/demo/label.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-back-to-top/demo/no-text.html b/elements/pf-back-to-top/demo/no-text.html deleted file mode 100644 index c2a4ed1db3..0000000000 --- a/elements/pf-back-to-top/demo/no-text.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

No Text

-

Focusable element (top)

- Scroll down to end of cyan box, 400px (default). -
-
-Focusable element (bottom) - - - - - - diff --git a/elements/pf-back-to-top/demo/scroll-distance.html b/elements/pf-back-to-top/demo/scroll-distance.html deleted file mode 100644 index 15c65c6a99..0000000000 --- a/elements/pf-back-to-top/demo/scroll-distance.html +++ /dev/null @@ -1,41 +0,0 @@ -
-
-

Default

-

Focusable element (top)

- Scroll down to end of cyan box, 200px (default). -
-
-Focusable element (bottom) - -Back to top - - - - diff --git a/elements/pf-back-to-top/docs/pf-back-to-top.md b/elements/pf-back-to-top/docs/pf-back-to-top.md deleted file mode 100644 index 525c404334..0000000000 --- a/elements/pf-back-to-top/docs/pf-back-to-top.md +++ /dev/null @@ -1,110 +0,0 @@ - - -{% renderOverview %} - Back to top button is designed to only be used once per page. - Back to top -{% endrenderOverview %} - -{% band header="Usage" %} - - ### Default - {% htmlexample %}Back to top{% endhtmlexample %} - - ### Label attribute - {% htmlexample %}{% endhtmlexample %} - - ### No text or label attribute - `[aria-label]` attribute defaults to text 'Back to top' - {% htmlexample %} - - - {% endhtmlexample %} - -
- - ### Scrollable Selector - - See [scrollable-selector](#attributes) in Attributes section below for more information. - - {% htmlexample %} -
-
-
- Scroll down to end of cyan box, 400px (default). -
-
- Back to top -
- {% endhtmlexample %} - - ### Scroll Distance - - See [scroll-distance](#attributes) in Attributes section below for more information. - - {% htmlexample %} -
-
-
- Scroll down to end of cyan box, 100px. -
-
- Back to top -
- {% endhtmlexample %} - -
- -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-back-to-top/pf-back-to-top.css b/elements/pf-back-to-top/pf-back-to-top.css deleted file mode 100644 index 4d74858c9d..0000000000 --- a/elements/pf-back-to-top/pf-back-to-top.css +++ /dev/null @@ -1,77 +0,0 @@ -:host { - display: inline-block; - position: absolute; - /** Right position for back to top */ - right: var(--pf-c-back-to-top--Right, var(--pf-global--spacer--2xl, 3rem)); - /** Bottom position for back to top */ - bottom: var(--pf-c-back-to-top--Bottom, var(--pf-global--spacer--lg, 1.5rem)); -} - -[part="trigger"] { - /** Box shadow for back to top button */ - box-shadow: var(--pf-c-back-to-top--c-button--BoxShadow, var(--pf-global--BoxShadow--lg-bottom, 0 0.75rem 0.75rem -0.5rem rgba(3, 3, 3, 0.18))); - - /** Font size for back to top button */ - --pf-c-button--FontSize: var(--pf-c-back-to-top--c-button--FontSize, var(--pf-global--FontSize--xs, 0.75rem)); - /** Border radius for back to top button */ - --pf-c-button--BorderRadius: var(--pf-c-back-to-top--c-button--BorderRadius, var(--pf-global--BorderRadius--lg, 30em)); - /** Top padding for back to top button */ - --pf-c-button--PaddingTop: var(--pf-c-back-to-top--c-button--PaddingTop, var(--pf-global--spacer--xs, 0.25rem)); - /** Right padding for back to top button */ - --pf-c-button--PaddingRight: var(--pf-c-back-to-top--c-button--PaddingRight, var(--pf-global--spacer--sm, 0.5rem)); - /** Bottom padding for back to top button */ - --pf-c-button--PaddingBottom: var(--pf-c-back-to-top--c-button--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem)); - /** Left padding for back to top button */ - --pf-c-button--PaddingLeft: var(--pf-c-back-to-top--c-button--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem)); -} - -a { - display: inline-flex; - align-items: center; - justify-content: center; - /** Primary color for back to top button */ - color: var(--pf-c-button--m-primary--Color, var(--pf-global--Color--light-100, #fff)); - /** Primary background color for back to top button */ - background-color: var(--pf-c-button--m-primary--BackgroundColor, var(--pf-global--primary-color--100, #06c)); - text-decoration: none; - font-size: var(--pf-c-button--FontSize); - padding: var(--pf-c-button--PaddingTop) var(--pf-c-button--PaddingRight) var(--pf-c-button--PaddingBottom) var(--pf-c-button--PaddingLeft); - border-radius: var(--pf-c-button--BorderRadius); - /** End icon margin left for back to top button */ - gap: var(--pf-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem)); -} - -a:hover, -a:focus { - --pf-c-button--m-primary--Color: var(--pf-c-button--m-primary--hover--Color, var(--pf-global--Color--light-100, #fff)); - --pf-c-button--m-primary--BackgroundColor: var(--pf-c-button--m-primary--hover--BackgroundColor, var(--pf-global--primary-color--200, #004080)); -} - -[part="trigger"]:is(pf-button):hover, -[part="trigger"]:is(pf-button):focus-within { - --pf-c-button--m-primary--BackgroundColor: var(--pf-c-button--m-primary--hover--BackgroundColor, var(--pf-global--primary-color--200, #004080)); - --pf-c-button--m-primary--Color: var(--pf-c-button--m-primary--hover--Color, var(--pf-global--Color--light-100, #fff)); -} - -[part="trigger"][hidden] { - display: none; -} - -pf-icon { - /* override icon size as default sm variant is incorrect */ - --pf-icon--size: var(--pf-c-button--FontSize); - vertical-align: -0.125rem; -} - -span { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--pf-c-button__icon--m-end--MarginLeft, var(--pf-global--spacer--xs, 0.25rem)); -} - -@media (min-width: 768px) { - :host { - --pf-c-back-to-top--Bottom: var(--pf-c-back-to-top--md--Bottom, var(--pf-global--spacer--2xl, 3rem)); - } -} diff --git a/elements/pf-back-to-top/pf-back-to-top.ts b/elements/pf-back-to-top/pf-back-to-top.ts deleted file mode 100644 index 346ea8d79f..0000000000 --- a/elements/pf-back-to-top/pf-back-to-top.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { LitElement, html, isServer, type PropertyValues, type TemplateResult } from 'lit'; -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 '@patternfly/elements/pf-button/pf-button.js'; -import '@patternfly/elements/pf-icon/pf-icon.js'; - -import styles from './pf-back-to-top.css'; - -/** - * The **back to top** component is a shortcut that allows users to quickly navigate to the top of a lengthy content page. - * @summary A shortcut that allows users to quickly navigate to the top of a lengthy content page. - * @alias Back to Top - */ -@customElement('pf-back-to-top') -export class PfBackToTop extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** Shorthand for the `icon` slot, the value is icon name */ - @property({ reflect: true }) icon?: string; - - /** Icon set for the `icon` property */ - @property({ attribute: 'icon-set' }) iconSet?: string; - - /** Flag to always show back to top button, defaults to false. */ - @property({ reflect: true, type: Boolean, attribute: 'always-visible' }) alwaysVisible = false; - - /** Element selector to spy on for scrolling. Not passing a selector defaults to spying on window scroll events */ - @property({ reflect: true, attribute: 'scrollable-selector' }) scrollableSelector?: string; - - /** Distance from the top of the scrollable element to trigger the visibility of the back to top button */ - @property({ type: Number, attribute: 'scroll-distance' }) scrollDistance = 400; - - /** Accessible name for the back-to-top link, use when component does not have slotted text */ - @property() label?: string; - - /** Page fragment link to target element, must include hash ex: #top */ - @property({ reflect: true }) href?: string; - - #scrollSpy = false; - - #visible = false; - - #scrollElement?: Element | Window; - - #hasSlottedText = false; - - #logger = new Logger(this); - - get #rootNode(): Document | ShadowRoot | null { - let root = null; - if (isServer) { - return null; - } else if ((root = this.getRootNode()) instanceof Document || root instanceof ShadowRoot) { - return root; - } else { - return document; - } - } - - get #ariaLabel(): string | undefined { - if (this.#hasSlottedText) { - return undefined; - } - return this.label ?? 'Back to top'; - } - - override connectedCallback(): void { - super.connectedCallback(); - this.#addScrollListener(); - } - - override disconnectedCallback(): void { - super.disconnectedCallback(); - this.#removeScrollListener(); - } - - override willUpdate(changed: PropertyValues): void { - if (changed.has('scrollableSelector')) { - this.#addScrollListener(); - } - if (changed.has('alwaysVisible')) { - this.#toggleVisibility(); - } - } - - render(): TemplateResult<1> { - // ensure href has a hash - if (this.href && this.href.charAt(0) !== '#') { - this.href = `#${this.href}`; - this.#logger.warn(`missing hash in href fragment link`); - } - - if (this.href) { - return html` - - - - - - - - - `; - } else { - return html` - - - - - - - - - - - `; - } - } - - #onSlotchange(event: Event) { - const slot = event.currentTarget as HTMLSlotElement; - const nodes = slot.assignedNodes(); - this.#hasSlottedText = nodes.length > 0 ? true : false; - this.requestUpdate(); - } - - #removeScrollListener() { - this.#scrollElement?.removeEventListener('scroll', this.#toggleVisibility); - } - - #addScrollListener() { - this.#removeScrollListener(); - - if (this.scrollableSelector?.trim() === '') { - this.#logger.error(`scrollable-selector attribute cannot be empty`); - return; - } - - this.#scrollSpy = !!this.scrollableSelector; - if (isServer) { - return; - } else if (this.#scrollSpy && this.scrollableSelector) { - const scrollableElement = this.#rootNode?.querySelector?.(this.scrollableSelector); - if (!scrollableElement) { - this.#logger.error(`unable to find element with selector ${this.scrollableSelector}`); - return; - } - this.#scrollElement = scrollableElement; - } else { - this.#scrollElement = window; - } - - this.#scrollElement.addEventListener('scroll', this.#toggleVisibility, { passive: true }); - this.#toggleVisibility(); - } - - #toggleVisibility = () => { - if (this.alwaysVisible) { - this.#visible = true; - this.requestUpdate(); - return; - } - const previousVisibility = this.#visible; - if (this.#scrollElement) { - const scrolled = - (this.#scrollElement instanceof Window) ? - this.#scrollElement.scrollY - : this.#scrollElement.scrollTop; - this.#visible = (scrolled > this.scrollDistance); - if (previousVisibility !== this.#visible) { - this.requestUpdate(); - } - } - }; -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-back-to-top': PfBackToTop; - } -} diff --git a/elements/pf-background-image/README.md b/elements/pf-background-image/README.md deleted file mode 100644 index c4dc0e7eaf..0000000000 --- a/elements/pf-background-image/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Background Image - -A **background image** allows you to place an image in the background of your page or area of a page. - - -## Installation -Load `` via CDN: - -```html - - - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-background-image/pf-background-image.js'; -``` - -## Usage - -```html - -``` - -[docs]: https://patternflyelements.org/components/background-image diff --git a/elements/pf-background-image/demo/index.html b/elements/pf-background-image/demo/index.html deleted file mode 100644 index 4427190c69..0000000000 --- a/elements/pf-background-image/demo/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/elements/pf-background-image/demo/sibling-content.html b/elements/pf-background-image/demo/sibling-content.html deleted file mode 100644 index 0bfc15f2b5..0000000000 --- a/elements/pf-background-image/demo/sibling-content.html +++ /dev/null @@ -1,30 +0,0 @@ - - - -
-

Sibling Content

- Button -
- - - - - - diff --git a/elements/pf-background-image/docs/pf-background-image.md b/elements/pf-background-image/docs/pf-background-image.md deleted file mode 100644 index 52f2db0eea..0000000000 --- a/elements/pf-background-image/docs/pf-background-image.md +++ /dev/null @@ -1,65 +0,0 @@ -{% renderOverview %} - {% htmlexample class="pf-background-image" %} - - {% endhtmlexample %} - - View the [full screen demo](/components/background-image/demo/). - -{% endrenderOverview %} - -{% band header="Usage" %} - ### Sibling content w/ no filter - {% htmlexample class="pf-background-image" %} - -

Sibling Content

- Button - {% endhtmlexample %} - - View the [full screen demo](/components/background-image/demo/sibling-content/). - - ### Override SVG Filter - - [MDN documentation for ``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter) - - {% htmlexample class="pf-background-image" %} - - - - - - - - {% endhtmlexample %} - - View the [full screen demo](/components/background-image/demo/filter-override/). - -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-background-image/pf-background-image.css b/elements/pf-background-image/pf-background-image.css deleted file mode 100644 index 027806a3e9..0000000000 --- a/elements/pf-background-image/pf-background-image.css +++ /dev/null @@ -1,73 +0,0 @@ -:host { - display: flex; - - /** Background image for the element */ - --_background-image: var(--pf-c-background-image--BackgroundImage); -} - -#outer-container { - display: contents; -} - -#container { - padding: 0; - margin: 0; - background-color: transparent; -} - -#container::after { - display: block; - position: fixed; - top: 0; - left: 0; - z-index: -1; - width: 100%; - height: 100%; - content: ""; - /** Background color for the background image */ - background-color: var(--pf-c-background-image--BackgroundColor, var(--pf-global--BackgroundColor--dark-100, #151515)); - background-image: var(--_background-image); - /** SVG filter applied to the background image */ - filter: var(--pf-c-background-image--Filter, url("#image_overlay")); - background-repeat: no-repeat; - background-size: cover; -} - -slot[name="filter"] { - display: none; -} - -slot[part="content"] { - display: block; - position: relative; - z-index: 1; - color: white; -} - -@media screen and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { - #container::after { - /** Background image for 2x DPI screens */ - background-image: var(--pf-c-background-image--BackgroundImage-2x, var(--_background-image-2x, var(--_background-image))); - } -} - -@media screen and (min-width: 576px) { - #container::after { - /** Background image for small screens */ - background-image: var(--pf-c-background-image--BackgroundImage--sm, var(--_background-image-sm, var(--_background-image))); - } -} - -@media screen and (min-width: 576px) and (-webkit-min-device-pixel-ratio: 2), (min-width: 576px) and (min-resolution: 192dpi) { - #container::after { - /** Background image for small screens with 2x DPI */ - background-image: var(--pf-c-background-image--BackgroundImage--sm-2x, var(--_background-image-sm-2x, var(--_background-image))); - } -} - -@media screen and (min-width: 992px) { - #container::after { - /** Background image for large screens */ - background-image: var(--pf-c-background-image--BackgroundImage--lg, var(--_background-image-lg, var(--_background-image))); - } -} diff --git a/elements/pf-background-image/pf-background-image.ts b/elements/pf-background-image/pf-background-image.ts deleted file mode 100644 index 1fa2627c80..0000000000 --- a/elements/pf-background-image/pf-background-image.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { queryAssignedElements } from 'lit/decorators/query-assigned-elements.js'; -import { property } from 'lit/decorators/property.js'; - - -import { styleMap, type StyleInfo } from 'lit/directives/style-map.js'; - -import styles from './pf-background-image.css'; - -/** - * A **background image** allows you to place an image in the background of your page or area of a page. - * @summary Allows users to place an image in the background of your page or area of a page. - * @alias Background Image - */ -@customElement('pf-background-image') -export class PfBackgroundImage extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** - * The URL for the image shown on mobile - */ - @property({ reflect: true }) src?: string; - - /** - * The image shown on mobile with 2x DPI - */ - @property({ reflect: true, attribute: 'src-2x' }) src2x?: string; - - /** - * The URL for the image shown on small screens (min-width: 576px) - */ - @property({ reflect: true, attribute: 'src-sm' }) srcSm?: string; - - /** - * The URL for the image shown on small screens (min-width: 576px) with 2x DPI - */ - @property({ reflect: true, attribute: 'src-sm-2x' }) srcSm2x?: string; - - /** - * The URL for the image shown on large screens (min-width: 992px) - */ - @property({ reflect: true, attribute: 'src-lg' }) srcLg?: string; - - /** - * Apply SVG Filter to the image - */ - @property({ type: Boolean, reflect: true }) filter = false; - - @queryAssignedElements({ slot: 'filter', selector: 'svg' }) private _svg?: SVGElement[]; - - #svg?: SVGElement; - - #updated = false; - - render(): TemplateResult<1> { - const cssProps = { - '--_background-image': this.src, - '--_background-image-2x': this.src2x, - '--_background-image-sm': this.srcSm, - '--_background-image-sm-2x': this.srcSm2x, - '--_background-image-lg': this.srcLg, - } as StyleInfo; - - Object.entries(cssProps).forEach(([key, value]) => { - // if the value is undefined, remove the css property - if (!value) { - delete cssProps[key]; - } else { - // otherwise, add the value with the url() css function - cssProps[key] = `url(${value})`; - } - }); - - return html` -
- ${!this.filter ? html`` : html` - - - ${(this.#svg && this.#updated) ? this.#svg : html` - - - - - - - - - - - - `} - - `} -
- `; - } - - #onSlotChange() { - const [svg] = this._svg as SVGElement[]; - if (svg) { - this.#svg = svg; - this.#updated = true; - this.requestUpdate(); - } else { - this.#updated = false; - } - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-background-image': PfBackgroundImage; - } -} diff --git a/elements/pf-badge/README.md b/elements/pf-badge/README.md deleted file mode 100644 index eea71ba5b7..0000000000 --- a/elements/pf-badge/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# PatternFly Elements Badge - -The `` component provides a way to have small numerical descriptors for -for for for UI elements. To provide context to your badge, it is highly -encouraged that you also include an `aria-label` attribute in your markup. - -Read more about Badge in the [PatternFly Elements Badge documentation](https://patternflyelements.org/components/badge) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-badge/pf-badge.js'; -``` - -## Usage - -```html -
- 2 -
-``` - -Please refer to the [specification](https://www.w3.org/TR/wai-aria/#aria-label) for additional details. - -With the `threshold` attribute: - -```html -
- 1 - 17 - 900 -
-``` - -With two state options for the `state` attribute: - -```html -
- 10 - 20 -
-``` - diff --git a/elements/pf-badge/demo/index.html b/elements/pf-badge/demo/index.html deleted file mode 100644 index be27b50dbb..0000000000 --- a/elements/pf-badge/demo/index.html +++ /dev/null @@ -1,12 +0,0 @@ -
- 613 -
- - - diff --git a/elements/pf-badge/demo/read.html b/elements/pf-badge/demo/read.html deleted file mode 100644 index 92a7897db3..0000000000 --- a/elements/pf-badge/demo/read.html +++ /dev/null @@ -1,15 +0,0 @@ -
- 7 - 24 - 240 - 999 -
- - - diff --git a/elements/pf-badge/demo/threshold.html b/elements/pf-badge/demo/threshold.html deleted file mode 100644 index 03b0eb7a5a..0000000000 --- a/elements/pf-badge/demo/threshold.html +++ /dev/null @@ -1,16 +0,0 @@ -
- 400 - 900 -

In this demo, the threshold is set to 500. - It should add '+' sign if the value exceeds the threshold. - It shouldn't add a '+' sign if the value doesn't exceed the threshold

-
- - - diff --git a/elements/pf-badge/demo/unread.html b/elements/pf-badge/demo/unread.html deleted file mode 100644 index 97d0f734ca..0000000000 --- a/elements/pf-badge/demo/unread.html +++ /dev/null @@ -1,15 +0,0 @@ -
- 7 - 24 - 240 - 999 -
- - - diff --git a/elements/pf-badge/docs/pf-badge.md b/elements/pf-badge/docs/pf-badge.md deleted file mode 100644 index 76e2175e6c..0000000000 --- a/elements/pf-badge/docs/pf-badge.md +++ /dev/null @@ -1,43 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - 17 - 900 - 10 -{% endrenderOverview %} - -{% band header="Usage" %} - To provide context to your badge, it is highly encouraged that you also include an `aria-label` attribute in your markup. - - ### Default - {% htmlexample %} - 2 - {% endhtmlexample %} - - ### With a threshold - This adds a "+" next to the number once the threshold value has been passed. - - {% htmlexample %} - 20 - {% endhtmlexample %} - - ### With a state - This adds a background color to the badge based on the state. - - {% htmlexample %} - 10 - 20 - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-badge/pf-badge.css b/elements/pf-badge/pf-badge.css deleted file mode 100644 index 1ff0ad30b2..0000000000 --- a/elements/pf-badge/pf-badge.css +++ /dev/null @@ -1,51 +0,0 @@ -:host { - position: relative; - white-space: nowrap; - text-align: center; - display: inline-block; - /** Border radius for badge */ - border-radius: var(--pf-c-badge--BorderRadius, - var(--pf-global--BorderRadius--lg, 180em)); - /** Minimum width for badge */ - min-width: var(--pf-c-badge--MinWidth, - var(--pf-global--spacer--xl, 2rem)); - /** Left padding for badge */ - padding-left: var(--pf-c-badge--PaddingLeft, - var(--pf-global--spacer--sm, 0.5rem)); - /** Right padding for badge */ - padding-right: var(--pf-c-badge--PaddingRight, - var(--pf-global--spacer--sm, 0.5rem)); - /** Font size for badge */ - font-size: var(--pf-c-badge--FontSize, - var(--pf-theme--font-size, 0.75em)); - /** Font weight for badge */ - font-weight: var(--pf-c-badge--FontWeight, - var(--pf-theme--font-weight--bold, 700)); - /** Line height for badge */ - line-height: var(--pf-c-badge--LineHeight, - var(--pf-global--LineHeight--md, 1.5)); - /** Color for badge */ - color: var(--pf-c-badge--Color, - var(--pf-global--palette--black-900, #151515)); - /** Background color for badge */ - background-color: var(--pf-c-badge--BackgroundColor, - var(--pf-global--palette--black-200, #f0f0f0)); -} - -:host([state="read"]) { - /** Color for read badge */ - --pf-c-badge--Color: var(--pf-c-badge--m-read--Color, - var(--pf-global--palette--black-900, #151515)); - /** Background color for read badge */ - --pf-c-badge--BackgroundColor: var(--pf-c-badge--m-read--BackgroundColor, - var(--pf-global--palette--black-200, #f0f0f0)); -} - -:host([state="unread"]) { - /** Color for unread badge */ - --pf-c-badge--Color: var(--pf-c-badge--m-unread--Color, - var(--pf-global--palette--white, #fff)); - /** Background color for unread badge */ - --pf-c-badge--BackgroundColor: var(--pf-c-badge--m-unread--BackgroundColor, - var(--pf-global--palette--blue-400, #06c)); -} diff --git a/elements/pf-badge/pf-badge.ts b/elements/pf-badge/pf-badge.ts deleted file mode 100644 index 3f50b336ba..0000000000 --- a/elements/pf-badge/pf-badge.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import styles from './pf-badge.css'; - -/** - * A **badge** is used to annotate other information like a label or an object name. - * @alias Badge - */ - - -@customElement('pf-badge') -export class PfBadge extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** - * Denotes the state-of-affairs this badge represents - * Options include read and unread - */ - @property({ reflect: true }) state?: 'unread' | 'read'; - - /** - * Sets a numeric value for a badge. - * - * You can pair it with `threshold` attribute to add a `+` sign - * if the number exceeds the threshold value. - */ - @property({ reflect: true, type: Number }) number?: number; - - /** - * Sets a threshold for the numeric value and adds `+` sign if - * the numeric value exceeds the threshold value. - */ - @property({ reflect: true, type: Number }) threshold?: number; - - override render(): TemplateResult<1> { - const { threshold, number, textContent } = this; - const displayText = - (threshold && number && (threshold < number)) ? `${threshold.toString()}+` - : (number != null) ? number.toString() - : textContent ?? ''; - return html` - ${displayText} - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-badge': PfBadge; - } -} diff --git a/elements/pf-badge/test/pf-badge.spec.ts b/elements/pf-badge/test/pf-badge.spec.ts deleted file mode 100644 index e47dd514d9..0000000000 --- a/elements/pf-badge/test/pf-badge.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { expect, html, nextFrame } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { hexToRgb, getColor } from '@patternfly/pfe-tools/test/hex-to-rgb.js'; -import { PfBadge } from '@patternfly/elements/pf-badge/pf-badge.js'; - -// Background colors for the various states -const states = { - default: '#f0f0f0', - read: '#f0f0f0', - unread: '#0066cc', -}; - -const element = html`10`; - -describe('', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-badge')).to.be.an.instanceof(PfBadge); - }); - - it('should upgrade', async function() { - const el = await createFixture(element); - expect(el, 'pf-badge should be an instance of PfBadge') - .to.be.an - .instanceOf(customElements.get('pf-badge')) - .and - .to.be.an.instanceof(PfBadge); - }); - - it('should display text equivalent to the number attribute', async function() { - const el = await createFixture(html`10`); - await nextFrame(); - expect(el.shadowRoot!.querySelector('span')!.textContent).to.equal('100'); - }); - - it('should add \'+\' sign if the value exceeds the threshold', async function() { - const el = await createFixture(html`900`); - await nextFrame(); - expect(el.shadowRoot!.querySelector('span')!.textContent).to.equal('100+'); - }); - - it('shouldn\'t add a \'+\' sign if the value doesn\'t exceed the threshold', async function() { - const el = await createFixture(html`900`); - await el.updateComplete; - expect(el.textContent).to.equal('900'); - expect(el.shadowRoot!.querySelector('span')!.textContent).to.equal('900'); - }); - - // This is the one that created an error: - Object.entries(states).forEach(([state, color]) => { - it(`should have a background color of '${color}' when state is ${state}`, async function() { - const el = await createFixture(element); - - if (state !== 'default') { - el.setAttribute('state', state); - } - - const [r, g, b] = getColor(el, 'background-color'); - expect([r, g, b]).to.deep.equal(hexToRgb(color)); - }); - }); -}); diff --git a/elements/pf-banner/README.md b/elements/pf-banner/README.md deleted file mode 100644 index 84c79e6b57..0000000000 --- a/elements/pf-banner/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Patternfly Elements Banner -A banner is a 1-line, full color, full width container that can be used to communicate short snippets of information to users. Banners are un-intrusive and non-dismissible. - -Read more about Button in the [PatternFly Elements Banner documentation](https://patternflyelements.org/components/banner) - -## Installation -Load `` via CDN: - -```html - -``` -Or, if you are using [NPM](https://npm.im/), install it - -```bash -npm install @patternfly/elements -``` -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-banner/pf-banner.js'; -``` - - -## Usage - -```html - - Default banner - -``` - - -## Attributes - -### Variant -Banners have five different variants. The available variants are `default`, `info`, `success`, `danger`, and `warning`. - -```html - - Info banner - -``` - -### Icon -Banners can have an icon attribute for a shorthand to Patternfly Icons. To see available icons, visit the [Patternfly Elements Icons documentation](https://patternflyelements.com/components/icons/). - -```html - - Info banner - -``` - -### Sticky -Banners can be sticky, so they stick to the top of the parent. - -```html - - Sticky banner - -``` diff --git a/elements/pf-banner/demo/index.html b/elements/pf-banner/demo/index.html deleted file mode 100644 index 6d00374a8d..0000000000 --- a/elements/pf-banner/demo/index.html +++ /dev/null @@ -1,15 +0,0 @@ -Default Banner -Blue Banner -Red Banner -Green Banner -Gold Banner - - - - diff --git a/elements/pf-banner/demo/status-banners.html b/elements/pf-banner/demo/status-banners.html deleted file mode 100644 index 76ad1aae57..0000000000 --- a/elements/pf-banner/demo/status-banners.html +++ /dev/null @@ -1,40 +0,0 @@ - - - Default Banner - - - - - Info Banner - - - - - Danger Banner - - - - - Success Banner - - - - - Warning Banner - - - - - diff --git a/elements/pf-banner/docs/pf-banner.md b/elements/pf-banner/docs/pf-banner.md deleted file mode 100644 index 30e91d1aae..0000000000 --- a/elements/pf-banner/docs/pf-banner.md +++ /dev/null @@ -1,59 +0,0 @@ -{% renderOverview %} - Default -{% endrenderOverview %} - -{% band header="Usage" %} - {% htmlexample %} - Default - {% endhtmlexample %} - - - ### With a variant - Available variants are: default, info, danger, success, warning - - {% htmlexample %} - Default - Info - Danger - Success - Warning - {% endhtmlexample %} - - - ### Sticky - Banners can be set to stick to the top of their container by adding the `sticky` attribute. - - {% htmlexample %} - Sticky - {% endhtmlexample %} - - - ### With an icon - You can add a icon by using the shorthand icon attribute - - {% htmlexample %} - Info - {% endhtmlexample %} - - Or you can use the icon slot and slot in a `svg` or `pf-icon` element - - {% htmlexample %} - - - Info - - {% endhtmlexample %} - -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-banner/pf-banner.css b/elements/pf-banner/pf-banner.css deleted file mode 100644 index 3ca89aba31..0000000000 --- a/elements/pf-banner/pf-banner.css +++ /dev/null @@ -1,117 +0,0 @@ -:host { - display: block; - /** Top padding for banner */ - --pf-c-banner--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - /** Right padding for banner */ - --pf-c-banner--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** Right padding for banner on medium screens */ - --pf-c-banner--md--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); - /** Bottom padding for banner */ - --pf-c-banner--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - /** Left padding for banner */ - --pf-c-banner--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** Left padding for banner on medium screens */ - --pf-c-banner--md--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); - /** Font size for banner */ - --pf-c-banner--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Text color for banner */ - --pf-c-banner--Color: var(--pf-global--Color--100, #151515); - /** Background color for banner */ - --pf-c-banner--BackgroundColor: var(--pf-global--BackgroundColor--dark-400, #4f5255); - /** Link color for banner (references banner color) */ - --pf-c-banner--link--Color: var(--pf-c-banner--Color); - /** Link text decoration for banner */ - --pf-c-banner--link--TextDecoration: underline; - /** Link hover color for banner (references banner color) */ - --pf-c-banner--link--hover--Color: var(--pf-c-banner--Color); - /** Link hover font weight for banner */ - --pf-c-banner--link--hover--FontWeight: var(--pf-global--FontWeight--semi-bold, 700); - /** Disabled link color for banner (references banner color) */ - --pf-c-banner--link--disabled--Color: var(--pf-c-banner--Color); - /** Disabled link text decoration for banner */ - --pf-c-banner--link--disabled--TextDecoration: none; - /** Background color for info banner */ - --pf-c-banner--m-info--BackgroundColor: var(--pf-global--palette--blue-200, #73bcf7); - /** Background color for danger banner */ - --pf-c-banner--m-danger--BackgroundColor: var(--pf-global--danger-color--100, #c9190b); - /** Background color for success banner */ - --pf-c-banner--m-success--BackgroundColor: var(--pf-global--success-color--100, #3e8635); - /** Background color for warning banner */ - --pf-c-banner--m-warning--BackgroundColor: var(--pf-global--warning-color--100, #f0ab00); - /** Z-index for sticky banner */ - --pf-c-banner--m-sticky--ZIndex: var(--pf-global--ZIndex--md, 300); - /** Box shadow for sticky banner */ - --pf-c-banner--m-sticky--BoxShadow: var(--pf-global--BoxShadow--md-bottom); -} - -#container, -#container.default { - color: var(--pf-global--Color--100, var(--pf-global--Color--light-100, #ffffff)); - overflow: hidden; - text-overflow: ellipsis; - padding: - var(--pf-c-banner--PaddingTop, var(--pf-global--spacer--xs, 0.25rem)) - var(--pf-c-banner--PaddingRight, var(--pf-global--spacer--md, 1rem)) - var(--pf-c-banner--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem)) - var(--pf-c-banner--PaddingLeft, var(--pf-global--spacer--md, 1rem)); - font-size: var(--pf-c-banner--FontSize, var(--pf-global--FontSize--sm, 0.875rem)); - color: var(--pf-global--Color--100, var(--pf-global--Color--light-100, #ffffff)); - white-space: nowrap; - background-color: var(--pf-c-banner--BackgroundColor, var(--pf-global--BackgroundColor--dark-400, #4f5255)); - - --pf-icon--size: 1em; -} - -#container.info { - color: var(--pf-global--Color--100, var(--pf-global--Color--dark-100, #151515)); - --pf-c-banner--BackgroundColor: var(--pf-c-banner--m-info--BackgroundColor, var(--pf-global--palette--blue-200, #73bcf7)); -} - -#container.danger { - --pf-c-banner--BackgroundColor: var(--pf-c-banner--m-danger--BackgroundColor, var(--pf-global--danger-color--100, #c9190b)); -} - -#container.success { - --pf-c-banner--BackgroundColor: var(--pf-c-banner--m-success--BackgroundColor, var(--pf-global--success-color--100, #3e8635)); -} - -#container.warning { - color: var(--pf-global--Color--100, var(--pf-global--Color--dark-100, #151515)); - --pf-c-banner--BackgroundColor: var(--pf-c-banner--m-warning--BackgroundColor, var(--pf-global--warning-color--100, #f0ab00)); -} - -#container.hasIcon { - display: var(--pf-l-flex--Display, flex); - flex-wrap: var(--pf-l-flex--FlexWrap, wrap); - align-items: var(--pf-l-flex--AlignItems, baseline); - gap: var(--pf-l-flex--spacer, var(--pf-l-flex--spacer--sm, var(--pf-global--spacer--sm, 0.5rem))); -} - -:host([sticky]) { - position: sticky; - top: 0; - z-index: var(--pf-c-banner--m-sticky--ZIndex, var(--pf-global--ZIndex--md, 300)); - box-shadow: - var(--pf-c-banner--m-sticky--BoxShadow, - var(--pf-global--BoxShadow--md-bottom, 0 0.5rem 0.5rem -0.375rem rgba(3, 3, 3, 0.18))); -} - -pf-icon, -::slotted(pf-icon), -::slotted(svg) { - position: relative; - inset-block-start: 0.125em; -} - -::slotted(svg) { - height: 1em; - width: 1em; - fill: currentcolor; -} - -@media (min-width: 768px) { - #container { - --pf-c-banner--PaddingRight: var(--pf-c-banner--md--PaddingRight, var(--pf-global--spacer--lg, 1.5rem)); - --pf-c-banner--PaddingLeft: var(--pf-c-banner--md--PaddingLeft, var(--pf-global--spacer--lg, 1.5rem)); - } -} diff --git a/elements/pf-banner/pf-banner.ts b/elements/pf-banner/pf-banner.ts deleted file mode 100644 index b81a9b797c..0000000000 --- a/elements/pf-banner/pf-banner.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; - -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import styles from './pf-banner.css'; - -export type BannerVariant = ( - | 'default' - | 'info' - | 'warning' - | 'danger' - | 'success' -); - -/** - * A banner is a 1-line, full color, full width container that can be used to communicate short snippets of information to users. - * Banners are un-intrusive and non-dismissible. - * @summary Allows users to display a stylized banner. - * @alias Banner - */ -@customElement('pf-banner') -export class PfBanner extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** Changes the visual appearance of the banner. */ - @property({ reflect: true }) variant?: BannerVariant; - - /** Shorthand for the `icon` slot, the value is icon name */ - @property() icon?: string; - - /** Shorthand for the `icon` slot, the value is icon name */ - @property({ type: Boolean }) sticky = false; - - /** Represents the state of the anonymous and icon slots */ - #slots = new SlotController(this, null, 'icon'); - - override willUpdate(changed: PropertyValues): void { - if (changed.has('icon') && this.icon) { - import('@patternfly/elements/pf-icon/pf-icon.js'); - } - } - - override render(): TemplateResult<1> { - const { variant, icon } = this; - const hasIcon = !!icon || this.#slots.hasSlotted('icon'); - return html` - -
- - ${!this.icon ? '' : html` - `} - - - -
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-banner': PfBanner; - } -} diff --git a/elements/pf-banner/test/pf-banner.spec.ts b/elements/pf-banner/test/pf-banner.spec.ts deleted file mode 100644 index 78fb74a32b..0000000000 --- a/elements/pf-banner/test/pf-banner.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { getColor, hexToRgb } from '@patternfly/pfe-tools/test/hex-to-rgb.js'; -import { PfBanner } from '@patternfly/elements/pf-banner/pf-banner.js'; - -describe('', function() { - describe('simply instantiating', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-banner')).to.be.an.instanceof(PfBanner); - }); - - it('should upgrade', async function() { - const el = await createFixture(html` - Default - `); - const klass = customElements.get('pf-banner'); - expect(el) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfBanner); - }); - - describe('without variant attribute', function() { - let element: PfBanner; - beforeEach(async function() { - element = await createFixture(html` - Default - `); - await element.updateComplete; - }); - // TODO: remove assertions on shadow roots - it('should display default variant', async function() { - const container = element.shadowRoot!.querySelector('#container')!; - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#ffffff')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#4f5255')); - }); - }); - - describe('with variant="info" attribute', function() { - let element: PfBanner; - beforeEach(async function() { - element = await createFixture(html` - Info - `); - await element.updateComplete; - }); - // TODO: remove assertions on shadow roots - it('should display info variant', async function() { - const container = element.shadowRoot!.querySelector('#container')!; - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#73bcf7')); - }); - }); - - describe('with variant="danger" attribute', function() { - let element: PfBanner; - beforeEach(async function() { - element = await createFixture(html` - Info - `); - await element.updateComplete; - }); - // TODO: remove assertions on shadow roots - it('should display danger variant', async function() { - const container = element.shadowRoot!.querySelector('#container')!; - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#ffffff')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#c9190b')); - }); - }); - - describe('with valid icon attribute', function() { - let element: PfBanner; - beforeEach(async function() { - element = await createFixture(html` - Info - `); - await element.updateComplete; - }); - // TODO: remove assertions on shadow roots - it('should render an icon', async function() { - const icon = element.shadowRoot!.querySelector('pf-icon'); - expect(icon).to.exist; - }); - }); - }); -}); diff --git a/elements/pf-button/README.md b/elements/pf-button/README.md deleted file mode 100644 index c1529c93f8..0000000000 --- a/elements/pf-button/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# PatternFly Elements Button - -`` is a web component wrapper around a standard HTML ` - ) -} -``` - - - - -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} diff --git a/elements/pf-button/pf-button-icon.css b/elements/pf-button/pf-button-icon.css deleted file mode 100644 index b20fa2abb5..0000000000 --- a/elements/pf-button/pf-button-icon.css +++ /dev/null @@ -1,11 +0,0 @@ -/****************************** - * * - * ICON * - * * - ******************************/ - -.hasIcon { - gap: calc(2 * var(--pf-c-button__icon--m-start--MarginLeft, - var(--pf-global--spacer--xs, 0.25rem))); -} - diff --git a/elements/pf-button/pf-button-tokens.css b/elements/pf-button/pf-button-tokens.css deleted file mode 100644 index ca24432b5d..0000000000 --- a/elements/pf-button/pf-button-tokens.css +++ /dev/null @@ -1,211 +0,0 @@ -:host { - /** PaddingTop for the button */ - --pf-c-button--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); - /** PaddingRight for the button */ - --pf-c-button--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the button */ - --pf-c-button--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); - /** PaddingLeft for the button */ - --pf-c-button--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** LineHeight for the button */ - --pf-c-button--LineHeight: var(--pf-global--LineHeight--md, 1.5); - /** FontWeight for the button */ - --pf-c-button--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** FontSize for the button */ - --pf-c-button--FontSize: var(--pf-global--FontSize--md, 1rem); - /** BackgroundColor for the button */ - --pf-c-button--BackgroundColor: transparent; - /** BorderRadius for the button */ - --pf-c-button--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - /** BorderRadius for the button after element */ - --pf-c-button--after--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - /** BorderColor for the button after element */ - --pf-c-button--after--BorderColor: transparent; - /** BorderWidth for the button after element */ - --pf-c-button--after--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderWidth for the button after element on hover */ - --pf-c-button--hover--after--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - /** BorderWidth for the button after element on focus */ - --pf-c-button--focus--after--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - /** BorderWidth for the button after element when active */ - --pf-c-button--active--after--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - /** Color for the button when disabled */ - --pf-c-button--disabled--Color: var(--pf-global--disabled-color--100, #6a6e73); - /** BackgroundColor for the button when disabled */ - --pf-c-button--disabled--BackgroundColor: var(--pf-global--disabled-color--200, #d2d2d2); - /** BorderColor for the button after element when disabled */ - --pf-c-button--disabled--after--BorderColor: transparent; - /** BackgroundColor for the primary button variant */ - --pf-c-button--m-primary--BackgroundColor: var(--pf-global--primary-color--100, #06c); - /** Color for the primary button variant */ - --pf-c-button--m-primary--Color: var(--pf-global--Color--light-100, #fff); - /** BackgroundColor for the primary button variant on hover */ - --pf-c-button--m-primary--hover--BackgroundColor: var(--pf-global--primary-color--200, #004080); - /** Color for the primary button variant on hover */ - --pf-c-button--m-primary--hover--Color: var(--pf-global--Color--light-100, #fff); - /** BackgroundColor for the primary button variant on focus */ - --pf-c-button--m-primary--focus--BackgroundColor: var(--pf-global--primary-color--200, #004080); - /** Color for the primary button variant on focus */ - --pf-c-button--m-primary--focus--Color: var(--pf-global--Color--light-100, #fff); - /** BackgroundColor for the primary button variant when active */ - --pf-c-button--m-primary--active--BackgroundColor: var(--pf-global--primary-color--200, #004080); - /** Color for the primary button variant when active */ - --pf-c-button--m-primary--active--Color: var(--pf-global--Color--light-100, #fff); - /** BackgroundColor for the secondary button variant */ - --pf-c-button--m-secondary--BackgroundColor: transparent; - /** BorderColor for the secondary button variant after element */ - --pf-c-button--m-secondary--after--BorderColor: var(--pf-global--primary-color--100, #06c); - /** Color for the secondary button variant */ - --pf-c-button--m-secondary--Color: var(--pf-global--primary-color--100, #06c); - /** BackgroundColor for the secondary button variant on hover */ - --pf-c-button--m-secondary--hover--BackgroundColor: transparent; - /** BorderColor for the secondary button variant after element on hover */ - --pf-c-button--m-secondary--hover--after--BorderColor: var(--pf-global--primary-color--100, #06c); - /** Color for the secondary button variant on hover */ - --pf-c-button--m-secondary--hover--Color: var(--pf-global--primary-color--100, #06c); - /** BackgroundColor for the secondary button variant on focus */ - --pf-c-button--m-secondary--focus--BackgroundColor: transparent; - /** BorderColor for the secondary button variant after element on focus */ - --pf-c-button--m-secondary--focus--after--BorderColor: var(--pf-global--primary-color--100, #06c); - /** Color for the secondary button variant on focus */ - --pf-c-button--m-secondary--focus--Color: var(--pf-global--primary-color--100, #06c); - /** BackgroundColor for the secondary button variant when active */ - --pf-c-button--m-secondary--active--BackgroundColor: transparent; - --pf-c-button--m-secondary--active--after--BorderColor: var(--pf-global--primary-color--100, #06c); - --pf-c-button--m-secondary--active--Color: var(--pf-global--primary-color--100, #06c); - --pf-c-button--m-secondary--m-danger--BackgroundColor: transparent; - --pf-c-button--m-secondary--m-danger--Color: var(--pf-global--danger-color--100, #c9190b); - --pf-c-button--m-secondary--m-danger--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-button--m-secondary--m-danger--hover--BackgroundColor: transparent; - --pf-c-button--m-secondary--m-danger--hover--Color: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-secondary--m-danger--hover--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-button--m-secondary--m-danger--focus--BackgroundColor: transparent; - --pf-c-button--m-secondary--m-danger--focus--Color: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-secondary--m-danger--focus--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-button--m-secondary--m-danger--active--BackgroundColor: transparent; - --pf-c-button--m-secondary--m-danger--active--Color: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-secondary--m-danger--active--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-button--m-tertiary--BackgroundColor: transparent; - --pf-c-button--m-tertiary--after--BorderColor: var(--pf-global--Color--100, #151515); - --pf-c-button--m-tertiary--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-tertiary--hover--BackgroundColor: transparent; - --pf-c-button--m-tertiary--hover--after--BorderColor: var(--pf-global--Color--100, #151515); - --pf-c-button--m-tertiary--hover--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-tertiary--focus--BackgroundColor: transparent; - --pf-c-button--m-tertiary--focus--after--BorderColor: var(--pf-global--Color--100, #151515); - --pf-c-button--m-tertiary--focus--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-tertiary--active--BackgroundColor: transparent; - --pf-c-button--m-tertiary--active--after--BorderColor: var(--pf-global--Color--100, #151515); - --pf-c-button--m-tertiary--active--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-warning--BackgroundColor: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-button--m-warning--Color: var(--pf-global--Color--dark-100, #151515); - --pf-c-button--m-warning--hover--BackgroundColor: var(--pf-global--palette--gold-500, #c58c00); - --pf-c-button--m-warning--hover--Color: var(--pf-global--Color--dark-100, #151515); - --pf-c-button--m-warning--focus--BackgroundColor: var(--pf-global--palette--gold-500, #c58c00); - --pf-c-button--m-warning--focus--Color: var(--pf-global--Color--dark-100, #151515); - --pf-c-button--m-warning--active--BackgroundColor: var(--pf-global--palette--gold-500, #c58c00); - --pf-c-button--m-warning--active--Color: var(--pf-global--Color--dark-100, #151515); - --pf-c-button--m-danger--BackgroundColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-button--m-danger--Color: var(--pf-global--Color--light-100, #fff); - --pf-c-button--m-danger--hover--BackgroundColor: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-danger--hover--Color: var(--pf-global--Color--light-100, #fff); - --pf-c-button--m-danger--focus--BackgroundColor: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-danger--focus--Color: var(--pf-global--Color--light-100, #fff); - --pf-c-button--m-danger--active--BackgroundColor: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-danger--active--Color: var(--pf-global--Color--light-100, #fff); - --pf-c-button--m-link--BackgroundColor: transparent; - --pf-c-button--m-link--Color: var(--pf-global--link--Color, #06c); - --pf-c-button--m-link--hover--BackgroundColor: transparent; - --pf-c-button--m-link--hover--Color: var(--pf-global--link--Color--hover, #004080); - --pf-c-button--m-link--focus--BackgroundColor: transparent; - --pf-c-button--m-link--focus--Color: var(--pf-global--link--Color--hover, #004080); - --pf-c-button--m-link--active--BackgroundColor: transparent; - --pf-c-button--m-link--active--Color: var(--pf-global--link--Color--hover, #004080); - --pf-c-button--m-link--disabled--BackgroundColor: transparent; - --pf-c-button--m-link--m-inline--FontSize: inherit; - --pf-c-button--m-link--m-inline--hover--TextDecoration: var(--pf-global--link--TextDecoration--hover, underline); - --pf-c-button--m-link--m-inline--hover--Color: var(--pf-global--link--Color--hover, #004080); - --pf-c-button--m-link--m-inline--PaddingTop: 0; - --pf-c-button--m-link--m-inline--PaddingRight: 0; - --pf-c-button--m-link--m-inline--PaddingBottom: 0; - --pf-c-button--m-link--m-inline--PaddingLeft: 0; - --pf-c-button--m-link--m-inline__progress--Left: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-button--m-link--m-inline--m-in-progress--PaddingLeft: calc(var(--pf-c-button--m-link--m-inline__progress--Left) + 1rem + var(--pf-global--spacer--sm, 0.5rem)); - --pf-c-button--m-link--m-danger--BackgroundColor: transparent; - --pf-c-button--m-link--m-danger--Color: var(--pf-global--danger-color--100, #c9190b); - --pf-c-button--m-link--m-danger--hover--BackgroundColor: transparent; - --pf-c-button--m-link--m-danger--hover--Color: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-link--m-danger--focus--BackgroundColor: transparent; - --pf-c-button--m-link--m-danger--focus--Color: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-link--m-danger--active--BackgroundColor: transparent; - --pf-c-button--m-link--m-danger--active--Color: var(--pf-global--danger-color--200, #a30000); - --pf-c-button--m-plain--BackgroundColor: transparent; - --pf-c-button--m-plain--Color: var(--pf-global--Color--200, #6a6e73); - --pf-c-button--m-plain--hover--BackgroundColor: transparent; - --pf-c-button--m-plain--hover--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-plain--focus--BackgroundColor: transparent; - --pf-c-button--m-plain--focus--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-plain--active--BackgroundColor: transparent; - --pf-c-button--m-plain--active--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-plain--disabled--Color: var(--pf-global--disabled-color--200, #d2d2d2); - --pf-c-button--m-plain--disabled--BackgroundColor: transparent; - --pf-c-button--m-control--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - --pf-c-button--m-control--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-control--BorderRadius: 0; - --pf-c-button--m-control--after--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-button--m-control--after--BorderTopColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-button--m-control--after--BorderRightColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-button--m-control--after--BorderBottomColor: var(--pf-global--BorderColor--200, #8a8d90); - --pf-c-button--m-control--after--BorderLeftColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-button--m-control--disabled--BackgroundColor: var(--pf-global--disabled-color--300, #f0f0f0); - --pf-c-button--m-control--hover--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - --pf-c-button--m-control--hover--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-control--hover--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-button--m-control--hover--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-button--m-control--active--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - --pf-c-button--m-control--active--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-control--active--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-button--m-control--active--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-button--m-control--focus--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - --pf-c-button--m-control--focus--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-control--focus--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-button--m-control--focus--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-button--m-control--m-expanded--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - --pf-c-button--m-control--m-expanded--Color: var(--pf-global--Color--100, #151515); - --pf-c-button--m-control--m-expanded--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-button--m-control--m-expanded--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-button--m-small--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - --pf-c-button--m-display-lg--PaddingTop: var(--pf-global--spacer--md, 1rem); - --pf-c-button--m-display-lg--PaddingRight: var(--pf-global--spacer--xl, 2rem); - --pf-c-button--m-display-lg--PaddingBottom: var(--pf-global--spacer--md, 1rem); - --pf-c-button--m-display-lg--PaddingLeft: var(--pf-global--spacer--xl, 2rem); - --pf-c-button--m-display-lg--FontWeight: var(--pf-global--FontWeight--bold, 700); - --pf-c-button--m-link--m-display-lg--FontSize: var(--pf-global--FontSize--lg, 1.125rem); - --pf-c-button__icon--m-start--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-button__icon--m-end--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-button__progress--width: calc(var(--pf-global--icon--FontSize--md, 1.125rem) + var(--pf-global--spacer--sm, 0.5rem)); - --pf-c-button__progress--Opacity: 0; - --pf-c-button__progress--TranslateY: -50%; - --pf-c-button__progress--TranslateX: 0; - --pf-c-button__progress--Top: 50%; - --pf-c-button__progress--Left: var(--pf-global--spacer--md, 1rem); - --pf-c-button--m-progress--TransitionProperty: padding; - --pf-c-button--m-progress--TransitionDuration: var(--pf-global--TransitionDuration, 250ms); - --pf-c-button--m-progress--PaddingRight: calc(var(--pf-global--spacer--md, 1rem) + var(--pf-c-button__progress--width) / 2); - --pf-c-button--m-progress--PaddingLeft: calc(var(--pf-global--spacer--md, 1rem) + var(--pf-c-button__progress--width) / 2); - --pf-c-button--m-in-progress--PaddingRight: var(--pf-global--spacer--md, 1rem); - --pf-c-button--m-in-progress--PaddingLeft: calc(var(--pf-global--spacer--md, 1rem) + var(--pf-c-button__progress--width)); - --pf-c-button--m-in-progress--m-plain--Color: var(--pf-global--primary-color--100, #06c); - --pf-c-button--m-in-progress--m-plain__progress--Left: 50%; - --pf-c-button--m-in-progress--m-plain__progress--TranslateX: -50%; - --pf-c-button__count--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-button--disabled__c-badge--Color: var(--pf-global--Color--dark-100, #151515); - --pf-c-button--disabled__c-badge--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - --pf-c-button--m-primary__c-badge--BorderColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-button--m-primary__c-badge--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - - /** Internal button color property */ - --_button-color: var(--pf-c-button--m-primary--Color); - /** Internal button background color property */ - --_button-background-color: var(--pf-c-button--m-primary--BackgroundColor); -} diff --git a/elements/pf-button/pf-button.css b/elements/pf-button/pf-button.css deleted file mode 100644 index 469bc55f4f..0000000000 --- a/elements/pf-button/pf-button.css +++ /dev/null @@ -1,611 +0,0 @@ -:host { - font-size: var(--pf-c-button--FontSize); - font-weight: var(--pf-c-button--FontWeight); - line-height: var(--pf-c-button--LineHeight); - text-align: center; - white-space: nowrap; - font-size: var(--pf-c-button--FontSize, - var(--pf-global--FontSize--md, 1rem)); - font-weight: var(--pf-c-button--FontWeight, - var(--pf-global--FontWeight--normal, 400)); - line-height: var(--pf-c-button--LineHeight, - var(--pf-global--LineHeight--md, 1.5)); - display: inline-block; - height: max-content; - cursor: pointer; - position: relative; - font-family: inherit; - border-width: 0; - border-style: solid; - border-radius: var(--pf-c-button--BorderRadius, - var(--pf-global--BorderRadius--sm, 3px)); -} - -:host([hidden]), -[hidden] { - display: none !important; -} - -:host([inline]) { - display: inline; - --pf-c-button--PaddingTop: 0; - --pf-c-button--PaddingLeft: 0; - --pf-c-button--PaddingBottom: 0; - --pf-c-button--PaddingRight: 0; -} - -pf-icon, -::slotted(pf-icon) { - color: currentcolor; - padding-inline-start: var(--_button-icon-padding-inline-start); - padding-inline-end: var(--_button-icon-padding-inline-end); - vertical-align: var(--_button-icon-vertical-align); -} - -#icon { - margin-inline-end: var(--pf-c-button__icon--m-start--MarginRight, - var(--pf-global--spacer--xs, 0.25rem)); -} - -#button { - display: inline-block; - color: var(--_button-color); - padding: - var(--pf-c-button--PaddingTop, - var(--pf-global--spacer--form-element, 0.375rem)) - var(--pf-c-button--PaddingRight, - var(--pf-global--spacer--md, 1rem)) - var(--pf-c-button--PaddingBottom, - var(--pf-global--spacer--form-element, 0.375rem)) - var(--pf-c-button--PaddingLeft, - var(--pf-global--spacer--md, 1rem)); - &::before, - &::after { - position: absolute; - inset: 0; - content: ""; - } - &::before { - background-color: var(--_button-background-color); - border-radius: var(--pf-c-button--BorderRadius, - var(--pf-global--BorderRadius--sm, 3px)); - } - &::after { - pointer-events: none; - border-style: solid; - border-width: var(--pf-c-button--after--BorderWidth); - border-color: var(--pf-c-button--after--BorderColor); - border-radius: var(--pf-c-button--after--BorderRadius); - } - &.anchor { - text-decoration: none; - &::after { - pointer-events: all; - } - } -} - -#text { - display: inline; - position: relative; -} - -:host(:focus) { - --pf-c-button--m-primary--Color: var(--pf-c-button--m-primary--focus--Color, - var(--pf-global--Color--light-100, #fff)); - --pf-c-button--m-primary--BackgroundColor: var(--pf-c-button--m-primary--focus--BackgroundColor, - var(--pf-global--primary-color--200, #004080)); - --pf-c-button--after--BorderWidth: var(--pf-c-button--focus--after--BorderWidth, - var(--pf-global--BorderWidth--md, 2px)); - /* DANGER */ - --pf-c-button--m-danger--Color: var(--pf-c-button--m-danger--focus--Color, - var(--pf-global--Color--light-100, #fff)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-danger--focus--BackgroundColor, - var(--pf-global--danger-color--200, #a30000)); - /* LINK */ - --pf-c-button--m-link--Color: var(--pf-c-button--m-link--focus--Color, - var(--pf-global--link--Color--hover, #004080)); - --pf-c-button--m-link--BackgroundColor: var(--pf-c-button--m-link--hover--BackgroundColor, transparent); - /* PLAIN */ - --pf-c-button--m-plain--Color: var(--pf-c-button--m-plain--focus--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-plain--BackgroundColor: var(--pf-c-button--m-plain--focus--BackgroundColor, - transparent); -} - -:host(:hover) { - --pf-c-button--m-primary--Color: var(--pf-c-button--m-primary--hover--Color, - var(--pf-global--Color--light-100, #fff)); - --pf-c-button--m-primary--BackgroundColor: var(--pf-c-button--m-primary--hover--BackgroundColor, - var(--pf-global--primary-color--200, #004080)); - --pf-c-button--after--BorderWidth: var(--pf-c-button--hover--after--BorderWidth, - var(--pf-global--BorderWidth--md, 2px)); - /* DANGER */ - --pf-c-button--m-danger--Color: var(--pf-c-button--m-danger--hover--Color, - var(--pf-global--Color--light-100, #fff)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-danger--hover--BackgroundColor, - var(--pf-global--danger-color--200, #a30000)); - /* LINK */ - --pf-c-button--m-link--Color: var(--pf-c-button--m-link--hover--Color, - var(--pf-global--link--Color--hover, #004080)); - --pf-c-button--m-link--BackgroundColor: var(--pf-c-button--m-link--hover--BackgroundColor, transparent); - /* PLAIN */ - --pf-c-button--m-plain--Color: var(--pf-c-button--m-plain--hover--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-plain--BackgroundColor: var(--pf-c-button--m-plain--hover--BackgroundColor, - transparent); -} - -:host(:active) { - --pf-c-button--m-primary--Color: var(--pf-c-button--m-primary--active--Color, - var(--pf-global--Color--light-100, #fff)); - --pf-c-button--m-primary--BackgroundColor: var(--pf-c-button--m-primary--active--BackgroundColor, - var(--pf-global--primary-color--200, #004080)); - --pf-c-button--after--BorderWidth: var(--pf-c-button--active--after--BorderWidth, - var(--pf-global--BorderWidth--md, 2px)); - /* DANGER */ - --pf-c-button--m-danger--Color: var(--pf-c-button--m-danger--active--Color, - var(--pf-global--Color--light-100, #fff)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-danger--active--BackgroundColor, - var(--pf-global--danger-color--200, #a30000)); - /* LINK */ - --pf-c-button--m-link--Color: var(--pf-c-button--m-link--active--Color, - var(--pf-global--link--Color--hover, #004080)); - --pf-c-button--m-link--BackgroundColor: var(--pf-c-button--m-link--active--BackgroundColor, transparent); -} - -.disabled, -:host(:disabled), -:host([danger]:disabled), -:host([link]:disabled) .link { - pointer-events: none; - cursor: default; -} - -[part=icon] { - --pf-icon--size: 16px; - display: inline-flex; - align-items: center; - position: absolute; - & ::slotted(*) { - width: 16px; - max-width: 16px; - height: 16px; - max-height: 16px; - } -} - -.hasIcon [part=icon] { - cursor: pointer; -} - -.hasIcon #button { - position: absolute; - inset: 0; -} - -/****************************** - * * - * PLAIN * - * * - ******************************/ - -#button.plain { - --pf-c-button--disabled--BackgroundColor: var(--pf-c-button--m-plain--disabled--BackgroundColor, transparent); - --pf-c-button--after--BorderWidth: 0 !important; - --pf-c-button--after--BorderColor: var(--pf-c-button--m-tertiary--after--BorderColor, - var(--pf-global--Color--100, #151515)); - --pf-c-button--disabled--Color: var(--pf-c-button--m-plain--disabled--Color, - var(--pf-global--disabled-color--200, #d2d2d2)); - --_button-color: var(--pf-c-button--m-plain--Color, - var(--pf-global--Color--200, #6a6e73)); - --_button-background-color: var(--pf-c-button--m-plain--BackgroundColor, - transparent); - :host(:active) & { - --pf-c-button--m-plain--Color: var(--pf-c-button--m-plain--active--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-plain--BackgroundColor: var(--pf-c-button--m-plain--active--BackgroundColor, - tranparent); - } - &:not(.hasIcon) [part=icon], - &.loading [part=icon] { - left: 16px; - } - & [part=icon] { - display: contents; - } - &.disabled, - &.link.disabled { - --_button-color: var(--pf-c-button--disabled--Color, - var(--pf-c-button--m-plain--disabled--Color, - var(--pf-global--disabled--color--200, #d2d2d2))); - } -} - -.hasIcon:not(.plain) [part=icon] { - position: relative; -} - -/****************************** - * * - * ICON POSITION RIGHT * - * * - ******************************/ - -:host([icon-position=right]) .loading [part=icon] { - order: 1; -} - -:host([icon-position=right]) #button.hasIcon { - padding-left: var(--pf-c-button--PaddingLeft, var(--pf-global--spacer--md, 1rem)); - padding-right: calc(16px + 8px + var(--pf-c-button--PaddingRight, var(--pf-global--spacer--md, 1rem))); -} - -/****************************** - * * - * WARNING * - * * - ******************************/ - -#button.warning { - --_button-color: var(--pf-c-button--m-warning--Color, - var(--pf-global--Color--dark-100, #151515)); - --_button-background-color: var(--pf-c-button--m-warning--BackgroundColor, - var(--pf-global--warning-color--100, #f0ab00)); - :host(:focus) & { - --pf-c-button--m-warning--Color: var(--pf-c-button--m-warning--focus--Color, - var(--pf-global--Color--dark-100, #151515)); - --pf-c-button--m-warning--BackgroundColor: var(--pf-c-button--m-warning--focus--BackgroundColor, - var(--pf-global--palette--gold-500, #c58c00)); - } - :host(:hover) & { - --pf-c-button--m-warning--Color: var(--pf-c-button--m-warning--hover--Color, - var(--pf-global--Color--dark-100, #151515)); - --pf-c-button--m-warning--BackgroundColor: var(--pf-c-button--m-warning--hover--BackgroundColor, - var(--pf-global--palette--gold-500, #c58c00)); - } - :host(:active) & { - --pf-c-button--m-warning--Color: var(--pf-c-button--m-warning--active--Color, - var(--pf-global--Color--dark-100, #151515)); - --pf-c-button--m-warning--BackgroundColor: var(--pf-c-button--m-warning--active--BackgroundColor, - var(--pf-global--palette--gold-500, #c58c00)); - } - &.disabled { - --_button-color: var(--pf-c-button--disabled--Color, - var(--pf-global--disabled-color--100, #6a6e73)); - --_button-background-color: var(--pf-c-button--disabled--BackgroundColor, - var(--pf-global--disabled-color--200, #d2d2d2)); - } -} - -/****************************** - * * - * LOADING * - * * - ******************************/ - -:host([loading]) #button { - position: relative; - display: flex; - align-items: center; -} - -#button.loading { - & [part=icon] { - display: inline-block; - z-index: 1; - position: absolute; - cursor: pointer; - top: var(--pf-c-button__progress--Top, 50%); - left: var(--pf-c-button__progress--Left, - var(--pf-global--spacer--md, 1rem)); - line-height: 1; - transform: translate( - var(--pf-c-button__progress--TranslateX, 0), - var(--pf-c-button__progress--TranslateY, -50%)); - margin-inline-end: var(--pf-c-button__icon--m-start--MarginRight, - var(--pf-global--spacer--xs, 0.25rem)); - } - &.primary:not(.plain), - &.danger { - --pf-c-spinner--Color: white; - } - &:not(.plain) { - padding-left: calc(12px + var(--pf-c-button--PaddingLeft, var(--pf-global--spacer--md, 1rem))); - --pf-c-button--PaddingRight: var(--pf-c-button--m-in-progress--PaddingRight, - var(--pf-global--spacer--md, 1rem)); - --pf-c-button--PaddingLeft: var(--pf-c-button--m-in-progress--PaddingLeft, - calc( - var(--pf-global--spacer--md, 1rem) + var(--pf-c-button__progress--width, - calc(var(--pf-global--icon--FontSize--md, 1.125rem) + var(--pf-global--spacer--sm, 0.5rem))) / 2)); - } - -} - -/****************************** - * * - * SECONDARY * - * * - ******************************/ - -#button.secondary { - --pf-c-button--m-danger--Color: var(--pf-c-button--m-secondary--m-danger--Color, - var(--pf-global--danger-color--100, #c9190b)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-secondary--m-danger--BackgroundColor, transparent); - --_button-color: var(--pf-c-button--m-secondary--Color, - var(--pf-global--primary-color--100, #06c)); - --_button-background-color: var(--pf-c-button--m-secondary--BackgroundColor, transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--after--BorderColor, - var(--pf-global--primary-color--100, #06c)); - :host(:focus) & { - --pf-c-button--m-secondary--Color: var(--pf-c-button--m-secondary--focus--Color, - var(--pf-global--primary-color--100, #06c)); - --pf-c-button--m-secondary--BackgroundColor: var(--pf-c-button--m-secondary--focus--BackgroundColor, transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--focus--after--BorderColor, - var(--pf-global--primary-color--100, #06c)); - /* DANGER */ - --pf-c-button--m-danger--Color: var(--pf-c-button--m-secondary--m-danger--focus--Color, - var(--pf-global--danger--color--200, #a30000)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-secondary--m-danger--focus--BackgroundColor, transparent); - } - :host(:hover) & { - --pf-c-button--m-secondary--Color: var(--pf-c-button--m-secondary--hover--Color, - var(--pf-global--primary-color--100, #06c)); - --pf-c-button--m-secondary--BackgroundColor: var(--pf-c-button--m-secondary--hover--BackgroundColor, - transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--hover--after--BorderColor, - var(--pf-global--primary-color--100, #06c)); - /* DANGER */ - --pf-c-button--m-danger--Color: var(--pf-c-button--m-secondary--m-danger--hover--Color, - var(--pf-global--danger--color--200, #a30000)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-secondary--m-danger--hover--BackgroundColor, transparent); - } - :host(:active) & { - --pf-c-button--m-secondary--Color: var(--pf-c-button--m-secondary--active--Color, - var(--pf-global--primary-color--100, #06c)); - --pf-c-button--m-secondary--BackgroundColor: var(--pf-c-button--m-secondary--active--BackgroundColor, transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--active--after--BorderColor, - var(--pf-global--primary-color--100, #06c)); - /* DANGER */ - --pf-c-button--m-danger--Color: var(--pf-c-button--m-secondary--m-danger--active--Color, - var(--pf-global--danger--color--200, #a30000)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-secondary--m-danger--active--BackgroundColor, transparent); - } - &.danger { - --_button-color: var(--pf-c-button--m-secondary--m-danger--Color, - var(--pf-global--danger--color--100, #c9190b)); - --_button-background-color: var(--pf-c-button--m-secondary--m-danger--BackgroundColor, transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--m-danger--after--BorderColor, - var(--pf-global--danger--color--100, #c9190b)); - :host(:focus) & { - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--m-danger--focus--after--BorderColor, - var(--pf-global--danger--color--100, #c9190b)); - } - :host(:hover) & { - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--m-danger--hover--after--BorderColor, - var(--pf-global--danger--color--100, #c9190b)); - } - :host(:active) & { - --pf-c-button--after--BorderColor: var(--pf-c-button--m-secondary--m-danger--active--after--BorderColor, - var(--pf-global--danger--color--100, #c9190b)); - } - } -} - -/****************************** - * * - * TERTIARY * - * * - ******************************/ - -#button.tertiary { - --pf-c-button--after--BorderColor: var(--pf-c-button--m-tertiary--after--BorderColor, - var(--pf-global--Color--100, #151515)); - --_button-color: var(--pf-c-button--m-tertiary--Color, - var(--pf-global--Color--100, #151515)); - --_button-background-color: var(--pf-c-button--m-tertiary--BackgroundColor, transparent); - :host(:focus) & { - --pf-c-button--m-tertiary--Color: var(--pf-c-button--m-tertiary--focus--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-tertiary--BackgroundColor: var(--pf-c-button--m-tertiary--focus--BackgroundColor, - transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-tertiary--focus--after--BorderColor, - var(--pf-global--Color--100, #151515)); - } - :host(:hover) & { - --pf-c-button--m-tertiary--Color: var(--pf-c-button--m-tertiary--hover--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-tertiary--BackgroundColor: var(--pf-c-button--m-tertiary--hover--BackgroundColor, transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-tertiary--hover--after--BorderColor, - var(--pf-global--Color--100, #151515)); - } - :host(:active) & { - --pf-c-button--m-tertiary--Color: var(--pf-c-button--m-tertiary--active--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-tertiary--BackgroundColor: var(--pf-c-button--m-tertiary--active--BackgroundColor, transparent); - --pf-c-button--after--BorderColor: var(--pf-c-button--m-tertiary--active--after--BorderColor, - var(--pf-global--Color--100, #151515)); - } -} - -/****************************** - * * - * CONTROL * - * * - ******************************/ - -#button.control { - --pf-c-button--BorderRadius: var(--pf-c-button--m-control--BorderRadius, 0); - --pf-c-button--disabled--BackgroundColor: var(--pf-c-button--m-control--disabled--BackgroundColor, - var(--pf-global--disabled-color--300, #f0f0f0)); - --pf-c-button--after--BorderRadius: 0; - --pf-c-button--after--BorderWidth: var(--pf-c-button--m-control--after--BorderWidth, - var(--pf-global--BorderWidth--sm, 1px)); - --pf-c-button--after--BorderColor: - var(--pf-c-button--m-control--after--BorderTopColor, - var(--pf-global--BorderColor--300, #f0f0f0)) - var(--pf-c-button--m-control--after--BorderRightColor, - var(--pf-global--BorderColor--300, #f0f0f0)) - var(--pf-c-button--m-control--after--BorderBottomColor, - var(--pf-global--BorderColor--200, #8a8d90)) - var(--pf-c-button--m-control--after--BorderLeftColor, - var(--pf-global--BorderColor--300, #f0f0f0)); - --_button-color: var(--pf-c-button--m-control--Color, - var(--pf-global--Color--100, #151515)); - --_button-background-color: var(--pf-c-button--m-control--BackgroundColor, - var(--pf-global--BackgroundColor--100, #fff)); - :host(:focus) & { - --pf-c-button--m-control--Color: var(--pf-c-button--m-control--focus--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-control--BackgroundColor: var(--pf-c-button--m-control--focus--BackgroundColor, - var(--pf-global--BackgroundColor--100, #fff)); - --pf-c-button--m-control--after--BorderBottomColor: var(--pf-c-button--m-control--focus--after--BorderBottomColor, - var(--pf-global--active-color--100, #06c)); - &::after { - border-block-end-width: var(--pf-c-button--m-control--focus--after--BorderBottomWidth, - var(--pf-global--BorderWidth--md, 2px)); - } - } - :host(:hover) & { - --pf-c-button--m-control--Color: var(--pf-c-button--m-control--hover--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-control--BackgroundColor: var(--pf-c-button--m-control--hover--BackgroundColor, - var(--pf-global--BackgroundColor--100, #fff)); - --pf-c-button--m-control--after--BorderBottomColor: var(--pf-c-button--m-control--hover--after--BorderBottomColor, - var(--pf-global--active-color--100, #06c)); - &::after { - border-block-end-width: var(--pf-c-button--m-control--hover--after--BorderBottomWidth, - var(--pf-global--BorderWidth--md, 2px)); - } - } - :host(:active) & { - --pf-c-button--m-control--Color: var(--pf-c-button--m-control--active--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-button--m-control--BackgroundColor: var(--pf-c-button--m-control--active--BackgroundColor, - var(--pf-global--BackgroundColor--100, #fff)); - --pf-c-button--m-control--after--BorderBottomColor: var(--pf-c-button--m-control--active--after--BorderBottomColor, - var(--pf-global--active-color--100, #06c)); - &::after { - border-block-end-width: var(--pf-c-button--m-control--active--after--BorderBottomWidth, - var(--pf-global--BorderWidth--md, 2px)); - } - } -} - -/****************************** - * * - * LINK * - * * - ******************************/ - -#button.link { - --pf-c-button--disabled--BackgroundColor: var(--pf-c-button--m-link--disabled--BackgroundColor, transparent); - --_button-color: var(--pf-c-button--m-link--Color, var(--pf-global--link--Color, #06c)); - --_button-background-color: var(--pf-c-button--m-link--BackgroundColor, - var(--pf-c-button--m-link--BackgroundColor, transparent)); - &.inline { - :host(:hover) & { - text-decoration: var(--pf-c-button--m-link--m-inline--hover--TextDecoration, - var(--pf-global--link--TextDecoration--hover, underline)); - } - } - &.danger { - --pf-c-button--m-danger--Color: var(--pf-c-button--m-link--m-danger--Color, - var(--pf-global--danger-color--100, #c9190b)); - --pf-c-button--m-danger--BackgroundColor: var(--pf-c-button--m-link--m-danger--BackgroundColor, transparent); - :host(:hover) & { - --pf-c-button--m-link--m-danger--Color: var(--pf-c-button--m-link--m-danger--hover--Color, - var(--pf-global--danger-color--200, #a30000)); - --pf-c-button--m-link--m-danger--BackgroundColor: var(--pf-c-button--m-link--m-danger--hover--BackgroundColor, transparent); - } - :host(:focus) & { - --pf-c-button--m-link--m-danger--Color: var(--pf-c-button--m-link--m-danger--focus--Color, - var(--pf-global--danger-color--200, #a30000)); - --pf-c-button--m-link--m-danger--BackgroundColor: var(--pf-c-button--m-link--m-danger--focus--BackgroundColor, transparent); - } - :host(:active) & { - --pf-c-button--m-link--m-danger--Color: var(--pf-c-button--m-link--m-danger--active--Color, - var(--pf-global--danger-color--200, #a30000)); - --pf-c-button--m-link--m-danger--BackgroundColor: var(--pf-c-button--m-link--m-danger--active--BackgroundColor, transparent); - } - } -} - -/****************************** - * * - * DISABLED * - * * - ******************************/ - -:host(:is(:disabled,[disabled])) { - pointer-events: none; - cursor: default; -} - -#button.disabled { - &:not(.plain) { - --_button-color: var(--pf-c-button--disabled--Color, - var(--pf-global--disabled-color--100, #6a6e73)); - --_button-background-color: var(--pf-c-button--disabled--BackgroundColor, - var(--pf-global--disabled-color--200, #d2d2d2)); - } - &::after { - border-color: var(--pf-c-button--disabled--after--BorderColor, transparent); - } -} - -/****************************** - * * - * BLOCK * - * * - ******************************/ - -:host([block]) { - display: flex; - width: 100%; - justify-content: center; -} - -/****************************** - * * - * LARGE * - * * - ******************************/ - -:host([size="large"]) { - --pf-c-button--PaddingTop: var(--pf-c-button--m-display-lg--PaddingTop, - var(--pf-global--spacer--md, 1rem)); - --pf-c-button--PaddingRight: var(--pf-c-button--m-display-lg--PaddingRight, - var(--pf-global--spacer--xl, 2rem)); - --pf-c-button--PaddingBottom: var(--pf-c-button--m-display-lg--PaddingBottom, - var(--pf-global--spacer--md, 1rem)); - --pf-c-button--PaddingLeft: var(--pf-c-button--m-display-lg--PaddingLeft, - var(--pf-global--spacer--xl, 2rem)); - --pf-c-button--FontWeight: var(--pf-c-button--m-display-lg--FontWeight, - var(--pf-global--FontWeight--bold, 700)); -} - -/****************************** - * * - * SMALL * - * * - ******************************/ - -:host([size="small"]) { - --pf-c-button--FontSize: var(--pf-c-button--m-small--FontSize, - var(--pf-global--FontSize--md, 1rem)); -} - -/****************************** - * * - * DANGER * - * * - ******************************/ - -#button.danger { - --_button-color: var(--pf-c-button--m-danger--Color, - var(--pf-global--Color--light-100, #fff)); - --_button-background-color: var(--pf-c-button--m-danger--BackgroundColor, - var(--pf-global--danger-color--100, #c9190b)); -} - diff --git a/elements/pf-button/pf-button.ts b/elements/pf-button/pf-button.ts deleted file mode 100644 index 83a0ad1940..0000000000 --- a/elements/pf-button/pf-button.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import '@patternfly/elements/pf-icon/pf-icon.js'; -import '@patternfly/elements/pf-spinner/pf-spinner.js'; - -import tokensStyles from './pf-button-tokens.css'; -import iconStyles from './pf-button-icon.css'; - -import styles from './pf-button.css'; - -export type ButtonVariant = ( - | 'primary' - | 'secondary' - | 'tertiary' - | 'control' - | 'link' -); - -/** - * A **button** is a box area or text that communicates and triggers user actions when - * clicked or selected. Buttons can be used to communicate and immediately trigger - * actions a user can take in an application, like submitting a form, canceling a - * process, or creating a new object. Buttons can also be used to take a user to a - * new location, like another page inside of a web application, or an external site - * such as help or documentation.. - * @summary Allows users to perform an action when triggered - * @alias Button - * @attr {string} [loading-label='loading'] - ARIA label for the loading indicator - */ -@customElement('pf-button') -export class PfButton extends LitElement { - static readonly formAssociated = true; - - static readonly styles: CSSStyleSheet[] = [ - tokensStyles, - iconStyles, - styles, - ]; - - @property({ reflect: true }) type?: 'button' | 'submit' | 'reset'; - - /** Accessible name for the button, use when the button does not have slotted text */ - @property() label?: string; - - /** Form value for the button */ - @property() value?: string; - - /** Form element name for the button */ - @property() name?: string; - - /** Disables the button */ - @property({ reflect: true, type: Boolean }) disabled = false; - - /** Represents the state of a stateful button */ - @property({ type: Boolean, reflect: true }) loading = false; - - /** Changes the size of the button. */ - @property({ reflect: true }) size?: 'small' | 'large'; - - /** Not as urgent as danger */ - @property({ type: Boolean, reflect: true }) warning = false; - - /** - * Use danger buttons for actions a user can take that are potentially - * destructive or difficult/impossible to undo, like deleting or removing - * user data. - */ - @property({ type: Boolean, reflect: true }) danger = false; - - /** Applies plain styles */ - @property({ type: Boolean, reflect: true }) plain = false; - - /** - * Changes the style of the button. - * - Primary: Used for the most important call to action on a page. Try to - * limit primary buttons to one per page. - * - Secondary: Use secondary buttons for general actions on a page, that - * don’t require as much emphasis as primary button actions. For example, - * you can use secondary buttons where there are multiple actions, like in - * toolbars or data lists. - * - Tertiary: Tertiary buttons are flexible and can be used as needed. - */ - @property({ reflect: true }) variant: ButtonVariant = 'primary'; - - @property({ reflect: true, type: Boolean }) inline = false; - - @property({ reflect: true, type: Boolean }) block = false; - - /** Shorthand for the `icon` slot, the value is icon name */ - @property() icon?: string; - - /** Icon set for the `icon` property */ - @property({ attribute: 'icon-set' }) iconSet?: string; - - /** Store the URL Link */ - @property({ reflect: true }) href?: string; - - /** Redirecting the URL Link to new Tab */ - @property({ reflect: true }) target?: string; - - #internals = InternalsController.of(this, { role: this.variant === 'link' ? 'none' : 'button' }); - - #slots = new SlotController(this, 'icon', null); - - get #disabled() { - return this.disabled || this.#internals.formDisabled; - } - - override connectedCallback(): void { - super.connectedCallback(); - this.addEventListener('click', this.#onClick); - this.addEventListener('keydown', this.#onKeydown); - } - - protected override willUpdate(): void { - this.#internals.ariaLabel = this.label || null; - this.#internals.ariaDisabled = String(!!this.disabled); - const isLink = this.variant === 'link' && this.href; - if (isLink) { - this.removeAttribute('tabindex'); - this.#internals.role = 'none'; - } else { - this.tabIndex = 0; - this.#internals.role = 'button'; - } - } - - async formDisabledCallback(): Promise { - await this.updateComplete; - this.requestUpdate(); - } - - override render(): TemplateResult<1> { - const hasIcon = !!this.icon || !!this.loading || this.#slots.hasSlotted('icon'); - const { warning, variant, danger, loading, plain, inline, block, size, href, target } = this; - - const disabled = this.#disabled; - - const content = html` - - - - - - `; - - if (variant === 'link' && href) { - return html` - ${content}`; - } else { - return html` -
${content}
`; - } - } - - #onClick() { - if (!this.#disabled) { - switch (this.type) { - case 'reset': - return this.#internals.reset(); - default: - return this.#internals.submit(); - } - } - } - - #onKeydown(event: KeyboardEvent) { - switch (event.key) { - case ' ': - event.preventDefault(); - event.stopPropagation(); - if (this.dispatchEvent(new MouseEvent('click', { - bubbles: true, - cancelable: true, - view: window, - }))) { - this.#onClick(); - } - break; - case 'Enter': - if (this.dispatchEvent(new MouseEvent('click', { - bubbles: true, - cancelable: true, - view: window, - }))) { - this.#onClick(); - } - } - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-button': PfButton; - } -} diff --git a/elements/pf-card/README.md b/elements/pf-card/README.md deleted file mode 100644 index c37fdab170..0000000000 --- a/elements/pf-card/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# PatternFly Elements Card - -Read more about Card in the [PatternFly Elements Card documentation](https://patternflyelements.org/components/card) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-card/pf-card.js'; -``` - -## Usage - -```html - -

Card header

-

This is the pf-card body.

- Footer link -
-``` - diff --git a/elements/pf-card/demo/index.html b/elements/pf-card/demo/index.html deleted file mode 100644 index 3f5df03b08..0000000000 --- a/elements/pf-card/demo/index.html +++ /dev/null @@ -1,15 +0,0 @@ - -

Header

- Body - Footer -
- - - - diff --git a/elements/pf-card/demo/modifiers.html b/elements/pf-card/demo/modifiers.html deleted file mode 100644 index d25fab3f30..0000000000 --- a/elements/pf-card/demo/modifiers.html +++ /dev/null @@ -1,107 +0,0 @@ -
-
- - - - - - - - - - - - - - - - - -
- -
- -

Lightest card

-

This is the lightest pf-card and a link, and a visited link with border.

- Try - Buy -
-
-
- - - - diff --git a/elements/pf-card/demo/ssr.html b/elements/pf-card/demo/ssr.html deleted file mode 100644 index 1713e50a47..0000000000 --- a/elements/pf-card/demo/ssr.html +++ /dev/null @@ -1,50 +0,0 @@ - -

Header

-

Body

- Footer -
- - -

Body

-
- - -

Header

-
- - -

Header

-

Body

-
- - -

Header

- Footer -
- - -

Body

- Footer -
- - - Footer - - - diff --git a/elements/pf-card/demo/title-inline-with-images-and-actions.html b/elements/pf-card/demo/title-inline-with-images-and-actions.html deleted file mode 100644 index 4157935d7e..0000000000 --- a/elements/pf-card/demo/title-inline-with-images-and-actions.html +++ /dev/null @@ -1,22 +0,0 @@ - -

This is a really really really really really really really really really really long header

- - - - Action - Link - Disabled Action - Disabled Link -
- Separated Action - Separated Link -
-
- Body - Footer -
- - diff --git a/elements/pf-card/docs/pf-card.md b/elements/pf-card/docs/pf-card.md deleted file mode 100644 index e8101deb2b..0000000000 --- a/elements/pf-card/docs/pf-card.md +++ /dev/null @@ -1,147 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - - Cards are flexible surfaces used to group information in a small layout. They give small previews of information or provide secondary content in relation to the content it's near. Several cards can be used together to group related information. - - -

Header

-

This is the default card

- Link in the footer -
-{% endrenderOverview %} - -{% band header="Usage" %} - - ### Basic cards - - ### Modifiers - {% htmlexample %} - -

Header

-

This is the compact card

- Link in the footer -
- {% endhtmlexample %} - - ### Large card - {% htmlexample %} - -

Large card

-

This is the large card

- Link in the footer -
- {% endhtmlexample %} - - ### Rounded card - {% htmlexample %} - -

Header

-

This is the rounded card

- Link in the footer -
- {% endhtmlexample %} - - ### Full Height card - {% htmlexample %} - -

Header

-

This is the full height card

- Link in the footer -
- {% endhtmlexample %} - - ### Plain card - {% htmlexample %} - -

Header

-

This is the plain card

- Link in the footer -
- {% endhtmlexample %} - - ### Header images and actions - You can include header images and actions in the `header` slot, along with a - title in the `title` slot. The following example includes an SVG image, and - also includes a kebab dropdown. - - {% htmlexample %} - - - - - - - - - Action - Link - Disabled Action - Disabled Link -
- Separated Action - Separated Link -
-
-

Title

- Body - Footer -
- {% endhtmlexample %} - - ### Title inline with images and actions - Slotting the `

` into the `header` slot, instead of the `title` slot will - style it inline with any images or actions. - - {% htmlexample %} - -

This is a really really really really really really really really really really long header

- - - - Action - Link - Disabled Action - Disabled Link -
- Separated Action - Separated Link -
-
- Body - Footer -
- {% endhtmlexample %} - -{% endband %} - -{% renderSlots %} - All slots are optional. If the slot is not defined, the content will be added to the `body` region of the card. -{% endrenderSlots %} - -{% renderAttributes %} - - #### `overflow` (child element attribute) - Optionally allows an image or element to overflow the padding on the container. This property should be added to the direct child of the slot such as on an image tag; should be added to the element that you want to overflow the container. Accepts: `top`, `right`, `bottom`, `left`. - -{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} - - diff --git a/elements/pf-card/pf-card.css b/elements/pf-card/pf-card.css deleted file mode 100644 index 23b0b5340c..0000000000 --- a/elements/pf-card/pf-card.css +++ /dev/null @@ -1,226 +0,0 @@ -:host { - /** BackgroundColor for the card */ - --pf-c-card--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - /** BoxShadow for the card */ - --pf-c-card--BoxShadow: var(--pf-global--BoxShadow--sm, 0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06)); - --pf-c-card--first-child--PaddingTop: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-card--child--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-card--child--PaddingBottom: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-card--child--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-card--c-divider--child--PaddingTop: var(--pf-global--spacer--lg, 1.5rem); - /** FontFamily for the card title */ - --pf-c-card__title--FontFamily: var(--pf-global--FontFamily--heading--sans-serif, "RedHatDisplay", "Overpass", overpass, helvetica, arial, sans-serif); - /** FontSize for the card title */ - --pf-c-card__title--FontSize: var(--pf-global--FontSize--md, 1rem); - /** FontWeight for the card title */ - --pf-c-card__title--FontWeight: var(--pf-global--FontWeight--bold, 700); - --pf-c-card__title--not--last-child--PaddingBottom: var(--pf-global--spacer--md, 1rem); - --pf-c-card__body--FontSize: var(--pf-global--FontSize--md, 1rem); - --pf-c-card__footer--FontSize: var(--pf-global--FontSize--md, 1rem); - --pf-c-card__actions--PaddingLeft: var(--pf-global--spacer--md, 1rem); - --pf-c-card__actions--child--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-card__header-toggle--MarginTop: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); - --pf-c-card__header-toggle--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-card__header-toggle--MarginBottom: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); - --pf-c-card__header-toggle--MarginLeft: calc(var(--pf-global--spacer--md, 1rem) * -1); - --pf-c-card__header-toggle-icon--Transition: var(--pf-global--Transition, all 250ms cubic-bezier(0.42, 0, 0.58, 1)); - --pf-c-card--m-expanded__header-toggle-icon--Rotate: 90deg; - --pf-c-card--m-hoverable--hover--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); - --pf-c-card--m-selectable--hover--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); - --pf-c-card--m-selectable--focus--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); - --pf-c-card--m-selectable--active--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); - --pf-c-card--m-selectable--m-selected--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); - --pf-c-card--m-selectable--m-selected--before--Height: var(--pf-global--BorderWidth--lg, 3px); - --pf-c-card--m-selectable--m-selected--before--BackgroundColor: var(--pf-global--active-color--100, #06c); - --pf-c-card--m-hoverable-raised--hover--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - --pf-c-card--m-hoverable-raised--hover--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); - --pf-c-card--m-selectable-raised--before--Right: 0; - --pf-c-card--m-selectable-raised--before--Bottom: 0; - --pf-c-card--m-selectable-raised--before--Left: 0; - --pf-c-card--m-flat--m-selectable-raised--before--Right: calc(-1 * var(--pf-c-card--m-flat--BorderWidth)); - --pf-c-card--m-flat--m-selectable-raised--before--Bottom: calc(-1 * var(--pf-c-card--m-flat--BorderWidth)); - --pf-c-card--m-flat--m-selectable-raised--before--Left: calc(-1 * var(--pf-c-card--m-flat--BorderWidth)); - --pf-c-card--m-selectable-raised--before--Height: var(--pf-global--BorderWidth--xl, 4px); - --pf-c-card--m-selectable-raised--before--BackgroundColor: transparent; - --pf-c-card--m-selectable-raised--before--Transition: none; - --pf-c-card--m-selectable-raised--before--ScaleY: 1; - --pf-c-card--m-selectable-raised--before--TranslateY: 0; - --pf-c-card--m-selectable-raised--hover--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - --pf-c-card--m-selectable-raised--hover--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); - --pf-c-card--m-selectable-raised--focus--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - --pf-c-card--m-selectable-raised--focus--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); - --pf-c-card--m-selectable-raised--active--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - --pf-c-card--m-selectable-raised--active--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); - --pf-c-card--m-selectable-raised--m-selected-raised--before--BackgroundColor: var(--pf-global--active-color--100, #06c); - --pf-c-card--m-selectable-raised--m-selected-raised--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); - --pf-c-card--m-selectable-raised--m-selected-raised--TranslateY--base: -0.5rem; - --pf-c-card--m-selectable-raised--m-selected-raised--TranslateY: var(--pf-c-card--m-selectable-raised--m-selected-raised--TranslateY--base); - --pf-c-card--m-flat--m-selectable-raised--m-selected-raised--TranslateY: calc(var(--pf-c-card--m-selectable-raised--m-selected-raised--TranslateY--base) + var(--pf-c-card--m-flat--BorderWidth)); - --pf-c-card--m-rounded--m-selectable-raised--m-selected-raised--TranslateY: calc(var(--pf-c-card--m-selectable-raised--m-selected-raised--TranslateY--base) + var(--pf-c-card--m-rounded--BorderRadius)); - --pf-c-card--m-selectable-raised--m-selected-raised--ZIndex: var(--pf-global--ZIndex--xs, 100); - --pf-c-card--m-selectable-raised--m-selected-raised--Transition: transform .25s linear, box-shadow .25s linear; - --pf-c-card--m-selectable-raised--m-selected-raised--before--Transition: transform .25s linear; - --pf-c-card--m-selectable-raised--m-selected-raised--before--TranslateY: calc(var(--pf-c-card--m-selectable-raised--m-selected-raised--TranslateY) * -1); - --pf-c-card--m-selectable-raised--m-selected-raised--before--ScaleY: 2; - --pf-c-card--m-non-selectable-raised--BackgroundColor: var(--pf-global--BackgroundColor--light-200, #fafafa); - --pf-c-card--m-non-selectable-raised--before--BackgroundColor: var(--pf-global--disabled-color--200, #d2d2d2); - --pf-c-card--m-non-selectable-raised--before--ScaleY: 2; - --pf-c-card--m-flat--m-non-selectable-raised--before--BorderColor: var(--pf-global--disabled-color--200, #d2d2d2); - /** FontSize for the card body in compact size */ - --pf-c-card--m-compact__body--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** FontSize for the card footer in compact size */ - --pf-c-card--m-compact__footer--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** PaddingTop for the first child in compact size */ - --pf-c-card--m-compact--first-child--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** PaddingRight for children in compact size */ - --pf-c-card--m-compact--child--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for children in compact size */ - --pf-c-card--m-compact--child--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** PaddingLeft for children in compact size */ - --pf-c-card--m-compact--child--PaddingLeft: var(--pf-global--spacer--md, 1rem); - --pf-c-card--m-compact--c-divider--child--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the title when not last child in compact size */ - --pf-c-card--m-compact__title--not--last-child--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); - /** FontSize for the title in large size */ - --pf-c-card--m-display-lg__title--FontSize: var(--pf-global--FontSize--xl, 1.25rem); - /** PaddingTop for the first child in large size */ - --pf-c-card--m-display-lg--first-child--PaddingTop: var(--pf-global--spacer--xl, 2rem); - /** PaddingRight for children in large size */ - --pf-c-card--m-display-lg--child--PaddingRight: var(--pf-global--spacer--xl, 2rem); - /** PaddingBottom for children in large size */ - --pf-c-card--m-display-lg--child--PaddingBottom: var(--pf-global--spacer--xl, 2rem); - /** PaddingLeft for children in large size */ - --pf-c-card--m-display-lg--child--PaddingLeft: var(--pf-global--spacer--xl, 2rem); - --pf-c-card--m-display-lg--c-divider--child--PaddingTop: var(--pf-global--spacer--xl, 2rem); - /** PaddingBottom for the title when not last child in large size */ - --pf-c-card--m-display-lg__title--not--last-child--PaddingBottom: var(--pf-global--spacer--lg, 1.5rem); - /** BorderWidth for the flat variant */ - --pf-c-card--m-flat--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderColor for the flat variant */ - --pf-c-card--m-flat--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** BorderRadius for the rounded variant */ - --pf-c-card--m-rounded--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - /** Height for the full height variant */ - --pf-c-card--m-full-height--Height: 100%; - /** BoxShadow for the plain variant */ - --pf-c-card--m-plain--BoxShadow: none; - /** BackgroundColor for the plain variant */ - --pf-c-card--m-plain--BackgroundColor: transparent; - --pf-c-card__header--m-toggle-right--toggle--MarginRight: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); - --pf-c-card__header--m-toggle-right--toggle--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-card__header--m-toggle-right--actions--MarginRight: 0; - --pf-c-card__input--focus--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-card__input--focus--BorderColor: var(--pf-global--primary-color--100, #06c); - display: flex; - flex-direction: column; - background-color: var(--pf-c-card--BackgroundColor); - box-shadow: var(--pf-c-card--BoxShadow); -} - -[hidden], -.empty { - display: none !important; -} - -header { - padding-block-start: var(--pf-c-card--first-child--PaddingTop); - padding-block-end: var(--pf-c-card__title--not--last-child--PaddingBottom); - display: flex; - flex-flow: row wrap; - align-items: center; -} - -header ::slotted(*) { - margin-block: 0 !important; - - font-family: var(--pf-c-card__title--FontFamily) !important; - font-size: var(--pf-c-card__title--FontSize) !important; - font-weight: var(--pf-c-card__title--FontWeight) !important; -} - -header ::slotted(pf-dropdown) { - margin-inline-start: auto; -} - -article { - position: relative; - height: 100%; - display: flex; - flex-direction: column; -} - -[part="header"], -[part="body"], -[part="footer"] { - padding-inline-start: var(--pf-c-card--child--PaddingLeft); - padding-inline-end: var(--pf-c-card--child--PaddingRight); - padding-block-end: var(--pf-c-card--child--PaddingBottom); -} - -#title { - display: block; - flex: 1 0 100%; - padding-block-start: var(--pf-c-card__title--not--last-child--PaddingBottom); -} - -[part="body"] { - font-size: var(--pf-c-card__body--FontSize); - flex: var(--pf-c-card__body--FullHeight--Flex); -} - -[part="body"] ::slotted(:not([slot]):first-of-type) { - margin-block-start: 0 !important; -} - -[part="body"] ::slotted(:not([slot]):last-of-type) { - margin-block-end: 0 !important; -} - -[part="footer"] { - margin-block-start: auto; - display: flex; - gap: 0.5em; - inset-block-end: 0; - font-size: var(--pf-c-card__footer--FontSize); -} - -:host([size="compact"]) { - --pf-c-card__body--FontSize: var(--pf-c-card--m-compact__body--FontSize); - --pf-c-card__footer--FontSize: var(--pf-c-card--m-compact__footer--FontSize); - --pf-c-card--first-child--PaddingTop: var(--pf-c-card--m-compact--first-child--PaddingTop); - --pf-c-card--child--PaddingRight: var(--pf-c-card--m-compact--child--PaddingRight); - --pf-c-card--child--PaddingBottom: var(--pf-c-card--m-compact--child--PaddingBottom); - --pf-c-card--child--PaddingLeft: var(--pf-c-card--m-compact--child--PaddingLeft); - --pf-c-card--c-divider--child--PaddingTop: var(--pf-c-card--m-compact--c-divider--child--PaddingTop); - --pf-c-card__title--not--last-child--PaddingBottom: var(--pf-c-card--m-compact__title--not--last-child--PaddingBottom); -} - -:host([size="large"]) { - --pf-c-card__title--FontSize: var(--pf-c-card--m-display-lg__title--FontSize); - --pf-c-card--first-child--PaddingTop: var(--pf-c-card--m-display-lg--first-child--PaddingTop); - --pf-c-card--child--PaddingRight: var(--pf-c-card--m-display-lg--child--PaddingRight); - --pf-c-card--child--PaddingBottom: var(--pf-c-card--m-display-lg--child--PaddingBottom); - --pf-c-card--child--PaddingLeft: var(--pf-c-card--m-display-lg--child--PaddingLeft); - --pf-c-card--c-divider--child--PaddingTop: var(--pf-c-card--m-display-lg--c-divider--child--PaddingTop); - --pf-c-card__title--not--last-child--PaddingBottom: var(--pf-c-card--m-display-lg__title--not--last-child--PaddingBottom); -} - -:host([flat]) { - --pf-c-card--BoxShadow: none; - border: var(--pf-c-card--m-flat--BorderWidth) solid var(--pf-c-card--m-flat--BorderColor); -} - -:host([plain]) { - --pf-c-card--BoxShadow: var(--pf-c-card--m-plain--BoxShadow); - --pf-c-card--BackgroundColor: var(--pf-c-card--m-plain--BackgroundColor); -} - -:host([rounded]) { - border-radius: var(--pf-c-card--m-rounded--BorderRadius); -} - -:host([full-height]) { - height: var(--pf-c-card--m-full-height--Height); - --pf-c-card__body--FullHeight--Flex: 1 1 auto; -} - diff --git a/elements/pf-card/pf-card.ts b/elements/pf-card/pf-card.ts deleted file mode 100644 index 250e8ca926..0000000000 --- a/elements/pf-card/pf-card.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import style from './pf-card.css'; - -/** - * A **card** is a square or rectangular container that can contain any kind of content. - * Cards symbolize units of information, and each one acts as an entry point for - * users to access more details. For example, in dashboards and catalog views, cards - * function as a preview of a detailed page. Cards may also be used in data displays - * like card views, or for positioning content on a page. - * @summary Gives a preview of information in a small layout - * @alias Card - */ -@customElement('pf-card') -export class PfCard extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - /** - * Optionally provide a size for the card and the card contents. - * The default is set to `undefined` and provides default styles. - * Compact provides styles which decreases the padding between the sections. - * Large provides styles which increases the padding between the sections and the font size for the title, header, and footer. - */ - @property({ reflect: true }) size?: 'compact' | 'large'; - - /** - * Optionally apply a border radius for the drop shadow and/or border. - */ - @property({ type: Boolean, reflect: true }) rounded = false; - - /** - * Optionally allow the card to take up the full height of the parent element. - */ - @property({ type: Boolean, reflect: true, attribute: 'full-height' }) fullHeight = false; - - /** - * Optionally remove the border on the card container. - */ - @property({ type: Boolean, reflect: true }) plain = false; - - #slots = new SlotController(this, 'header', 'title', null, 'footer'); - - render(): TemplateResult<1> { - return html` -
- - - -
- - -
- -
- - -
-
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-card': PfCard; - } -} diff --git a/elements/pf-chip/README.md b/elements/pf-chip/README.md deleted file mode 100644 index e0982800f9..0000000000 --- a/elements/pf-chip/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Chip - -A **chip** is used to communicate a value or a set of attribute-value pairs within workflows that involve filtering a set of objects. - -## Usage - -Describe how best to use this web component along with best practices. - -```html -Chip 1 -``` - -```html - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - -``` diff --git a/elements/pf-chip/demo/chip-group-closeable.html b/elements/pf-chip/demo/chip-group-closeable.html deleted file mode 100644 index 93b59a4de7..0000000000 --- a/elements/pf-chip/demo/chip-group-closeable.html +++ /dev/null @@ -1,10 +0,0 @@ - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - - diff --git a/elements/pf-chip/demo/chip-group-open.html b/elements/pf-chip/demo/chip-group-open.html deleted file mode 100644 index c7dc7febe1..0000000000 --- a/elements/pf-chip/demo/chip-group-open.html +++ /dev/null @@ -1,10 +0,0 @@ - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - - diff --git a/elements/pf-chip/demo/chip-group-with-accessible-label.html b/elements/pf-chip/demo/chip-group-with-accessible-label.html deleted file mode 100644 index 86a53094e3..0000000000 --- a/elements/pf-chip/demo/chip-group-with-accessible-label.html +++ /dev/null @@ -1,10 +0,0 @@ - -

Chip group, with off-screen category label (for screen readers)

- - Chip 1 - Chip 2 - Chip 3 - Chip 4 - \ No newline at end of file diff --git a/elements/pf-chip/demo/chip-group-with-custom-number-of-chips.html b/elements/pf-chip/demo/chip-group-with-custom-number-of-chips.html deleted file mode 100644 index f5ac808601..0000000000 --- a/elements/pf-chip/demo/chip-group-with-custom-number-of-chips.html +++ /dev/null @@ -1,10 +0,0 @@ - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - - diff --git a/elements/pf-chip/demo/chip-group-with-visible-category-name.html b/elements/pf-chip/demo/chip-group-with-visible-category-name.html deleted file mode 100644 index 7945ad06de..0000000000 --- a/elements/pf-chip/demo/chip-group-with-visible-category-name.html +++ /dev/null @@ -1,11 +0,0 @@ - - Category three - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - - diff --git a/elements/pf-chip/demo/chip-group.html b/elements/pf-chip/demo/chip-group.html deleted file mode 100644 index 76c633e7fb..0000000000 --- a/elements/pf-chip/demo/chip-group.html +++ /dev/null @@ -1,10 +0,0 @@ - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - - diff --git a/elements/pf-chip/demo/index.html b/elements/pf-chip/demo/index.html deleted file mode 100644 index 3bfe870e48..0000000000 --- a/elements/pf-chip/demo/index.html +++ /dev/null @@ -1,6 +0,0 @@ -Chip 1 - - - diff --git a/elements/pf-chip/demo/long-chip-with-tooltip.html b/elements/pf-chip/demo/long-chip-with-tooltip.html deleted file mode 100644 index 201b11ad74..0000000000 --- a/elements/pf-chip/demo/long-chip-with-tooltip.html +++ /dev/null @@ -1,21 +0,0 @@ - - Really long chip that goes on and on - Really long chip that goes on and on - - - - - - diff --git a/elements/pf-chip/demo/overflow.html b/elements/pf-chip/demo/overflow.html deleted file mode 100644 index 720b12ba7a..0000000000 --- a/elements/pf-chip/demo/overflow.html +++ /dev/null @@ -1,6 +0,0 @@ -Overflow chip - - - diff --git a/elements/pf-chip/demo/read-only.html b/elements/pf-chip/demo/read-only.html deleted file mode 100644 index 073de149c0..0000000000 --- a/elements/pf-chip/demo/read-only.html +++ /dev/null @@ -1,8 +0,0 @@ -Read-only chip - - - - - diff --git a/elements/pf-chip/demo/with-badge.html b/elements/pf-chip/demo/with-badge.html deleted file mode 100644 index 330fe64dab..0000000000 --- a/elements/pf-chip/demo/with-badge.html +++ /dev/null @@ -1,8 +0,0 @@ -Chip 7 - - - - diff --git a/elements/pf-chip/docs/pf-chip.md b/elements/pf-chip/docs/pf-chip.md deleted file mode 100644 index b468ee2896..0000000000 --- a/elements/pf-chip/docs/pf-chip.md +++ /dev/null @@ -1,139 +0,0 @@ - - -{% renderOverview %} - Chip 1 -{% endrenderOverview %} - -{% band header="Usage" %} - -### Default - -{% htmlexample %} - Chip 1 - Chip 7 -{% endhtmlexample %} - -### Chip variants - -Chips can be removable or read-only. The Overflow chip is a special chip that is used to expand or collapse the content of a chip group. - -#### Read-only - -{% htmlexample %} - Read-only chip -{% endhtmlexample %} - -#### Overflow chip - -{% htmlexample %} - Overflow-chip -{% endhtmlexample %} - -#### Max-width - -{% htmlexample %} - - Really long chip that goes on and on - Really long chip that goes on and on - -{% endhtmlexample %} - -### Chip group - -A **chip group** is a collection of chips that can be grouped by category and used to represent one or more values assigned to a single attribute. When the value of `numChips` is exceeded, additional chips will be hidden using an overflow chip. - -Chip groups are typically used in filter and selection use cases to indicate to the user what selections they have made. They separate selections by attribute, for added clarity. An OR relationship is implied between values in the group. Chip groups also give users the ability to either delete an entire chip group at once using the group X, or delete individual chips at a time. - -Chip groups are useful to express complex filters to a data set, for example. - -{% htmlexample %} - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - -{% endhtmlexample %} - -#### Chip group with categories - -{% htmlexample %} - - Category one - Chip 1 - Chip 2 - Chip 3 - Chip 4 - -

- - Category two - Chip 5 - Chip 6 - Chip 7 - Chip 8 - -{% endhtmlexample %} - -#### Chip group with removable categories - -{% htmlexample %} - - Category three - Chip 1 - Chip 2 - Chip 3 - Chip 4 - -{% endhtmlexample %} - -{% endband %} - -{% band header="Accessibility" %} - -The select uses the [Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) recommendations from the WAI ARIA [Authoring Best Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg). - -#### Chips - -When focus is on an individual chip that is not `readonly`, the following keyboard interactions apply: - -| Key | Function | -| ---------------------- | ----------------------------------------------------------------------------------- | -| Enter | On an overflow chip, shows/hides additional chips. Otherwise removes a chip. | -| Space | On an overflow chip, shows/hides additional chips. Otherwise removes a chip. | - -#### Chip groups - -Chip groups use the [APG's Roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) recommendation. When focus in a chip group, the following keyboard interactions apply: - -| Key | Function | -| ---------------------- | ----------------------------------------------------------------------------------- | -| Enter | When focus is on a group's remove botton, removes the group. | -| Space | When focus is on a group's remove botton, removes the group. | -| Up Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | -| Down Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | -| Left Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | -| Right Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | -| Home | Moves focus to the first item in the current group. | - -{% endband %} - -{% renderSlots for="pf-chip", header="Slots on `pf-chip`" %}{% endrenderSlots %} -{% renderAttributes for="pf-chip", header="Attributes on `pf-chip`" %}{% endrenderAttributes %} -{% renderMethods for="pf-chip", header="Methods on `pf-chip`" %}{% endrenderMethods %} -{% renderEvents for="pf-chip", header="Events on `pf-chip`" %}{% endrenderEvents %} -{% renderCssCustomProperties for="pf-chip", header="CSS Custom Properties on `pf-chip`" %}{% endrenderCssCustomProperties %} -{% renderCssParts for="pf-chip", header="CSS Parts on `pf-chip`" %}{% endrenderCssParts %} - -{% renderSlots for="pf-chip-group", header="Slots on `pf-chip-group`" %}{% endrenderSlots %} -{% renderAttributes for="pf-chip-group", header="Attributes on `pf-chip-group`" %}{% endrenderAttributes %} -{% renderMethods for="pf-chip-group", header="Methods on `pf-chip-group`" %}{% endrenderMethods %} -{% renderEvents for="pf-chi-group", header="Events on `pf-chip-group`" %}{% endrenderEvents %} -{% renderCssCustomProperties for="pf-chip-group", header="CSS Custom Properties on `pf-chip`" %}{% endrenderCssCustomProperties %} -{% renderCssParts for="pf-chip-group", header="CSS Parts on `pf-chip-group`" %}{% endrenderCssParts %} diff --git a/elements/pf-chip/pf-chip-group.css b/elements/pf-chip/pf-chip-group.css deleted file mode 100644 index c3a5ecbf66..0000000000 --- a/elements/pf-chip/pf-chip-group.css +++ /dev/null @@ -1,109 +0,0 @@ -:host { - /** MarginBottom for the chip group list */ - --pf-c-chip-group__list--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - /** MarginRight for the chip group list */ - --pf-c-chip-group__list--MarginRight: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - /** PaddingTop for the chip group category */ - --pf-c-chip-group--m-category--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - /** PaddingRight for the chip group category */ - --pf-c-chip-group--m-category--PaddingRight: var(--pf-global--spacer--xs, 0.25rem); - /** PaddingBottom for the chip group category */ - --pf-c-chip-group--m-category--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - /** PaddingLeft for the chip group category */ - --pf-c-chip-group--m-category--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - /** BorderRadius for the chip group category */ - --pf-c-chip-group--m-category--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - /** BackgroundColor for the chip group category */ - --pf-c-chip-group--m-category--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - /** MarginRight for the chip group label */ - --pf-c-chip-group__label--MarginRight: var(--pf-global--spacer--sm, 0.5rem); - /** FontSize for the chip group label */ - --pf-c-chip-group__label--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** MaxWidth for the chip group label */ - --pf-c-chip-group__label--MaxWidth: 18ch; - /** MarginTop for the chip group close button */ - --pf-c-chip-group__close--MarginTop: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - /** MarginBottom for the chip group close button */ - --pf-c-chip-group__close--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - /** MarginRight for chip group list items */ - --pf-c-chip-group__list-item--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - /** MarginBottom for chip group list items */ - --pf-c-chip-group__list-item--MarginBottom: var(--pf-global--spacer--xs, 0.25rem); - display: inline-flex; - flex-wrap: wrap; - align-items: center; - min-width: 0; - max-width: 100%; - color: var(--pf-global--Color--100, #151515); -} - -[hidden], -.empty, -::slotted([overflow-hidden]) { - display: none !important; -} - -#outer { - display: flex; - flex: 1; - flex-wrap: wrap; - align-items: baseline; - min-width: 0; - display: inline-flex; - column-gap: var(--pf-global--spacer--xs, 0.25rem); - align-items: center; - border-radius: var(--pf-global--BorderRadius--sm, 3px); - padding: 0 !important; -} - -#outer.has-category { - padding: var(--pf-global--spacer--xs, 0.25rem) var(--pf-global--spacer--sm, 0.5rem) !important; - background-color: var(--pf-theme--color--surface--lighter, #f0f0f0); -} - -#chips { - margin-right: var(--pf-c-chip-group__list--MarginRight); - margin-bottom: var(--pf-c-chip-group__list--MarginBottom); - font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); - font-size: var(--pf-global--FontSize--sm, 14px); - font-weight: var(--pf-global--FontWeight--normal, 400); - line-height: 1.6; -} - -#chips ::slotted(pf-chip) { - display: inline-flex; - min-width: 0; - margin-right: var(--pf-c-chip-group__list-item--MarginRight); - margin-bottom: var(--pf-c-chip-group__list-item--MarginBottom); -} - -#close-button { - --pf-icon--size: 16px; - margin: var(--pf-c-chip__c-button--MarginTop) - var(--pf-c-chip__c-button--MarginRight) - var(--pf-c-chip__c-button--MarginBottom) - var(--pf-c-chip__c-button--MarginLeft); - padding: var(--pf-c-chip__c-button--PaddingTop) - var(--pf-c-chip__c-button--PaddingRight) - var(--pf-c-chip__c-button--PaddingBottom) - var(--pf-c-chip__c-button--PaddingLeft); - inset-block-start: 0.125em; -} - -svg { - width: var(--pf-global--FontSize--sm, 14px); - height: var(--pf-global--FontSize--sm, 14px); -} - -.visually-hidden { - border: 0; - clip: rect(0, 0, 0, 0); - block-size: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - inline-size: 1px; -} - diff --git a/elements/pf-chip/pf-chip-group.ts b/elements/pf-chip/pf-chip-group.ts deleted file mode 100644 index 16c6795797..0000000000 --- a/elements/pf-chip/pf-chip-group.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { query } from 'lit/decorators/query.js'; -import { queryAssignedNodes } from 'lit/decorators/query-assigned-nodes.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { observes } from '@patternfly/pfe-core/decorators/observes.js'; -import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; - -import { PfChip, PfChipRemoveEvent } from './pf-chip.js'; - -import styles from './pf-chip-group.css'; -import shared from './pf-chip-shared.css'; - -export class PfChipGroupExpandEvent extends Event { - constructor() { - super('expand', { bubbles: true, cancelable: true }); - } -} - -export class PfChipGroupRemoveEvent extends Event { - constructor() { - super('remove', { bubbles: true, cancelable: true }); - } -} - -/** - * `${` - * **WS** (_>= 0x_) - * `remaining` - * **WS** (_>= 0x_) - * `}` - */ -const REMAINING_RE = /\$\{\s*remaining\s*\}/g; - -/** - * A **chip group** is a collection of chips that can be grouped by category and used to represent one or more values assigned to a single attribute. When value of numChips is exceeded, additional chips will be hidden using an overflow chip. - * @fires expand - Fires when chip group is expanded to show all chips - * @fires remove - Fires when chip group is closed/removed - * @slot category-name - * Category name text for chip group category. If this prop is supplied chip group with have a label and category styling applied - * @slot - `` elements. - */ -@customElement('pf-chip-group') -export class PfChipGroup extends LitElement { - static readonly styles: CSSStyleSheet[] = [shared, styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** - * Accessible label for chip group that does not have a category name - */ - @property({ attribute: 'accessible-label', type: String }) accessibleLabel = ''; - - /** - * Accessible label for close button - */ - @property({ attribute: 'accessible-close-label', type: String }) accessibleCloseLabel = 'Close'; - - /** - * Customizeable "more" template string. Use variable "${remaining}" for overflow chip count. - */ - @property({ attribute: 'collapsed-text', type: String }) collapsedText = '${remaining} more'; - - /** - * Customizable "show less" text string. - */ - @property({ attribute: 'expanded-text', type: String }) expandedText = 'show less'; - - /** - * Set number of chips to show before overflow - */ - @property({ attribute: 'num-chips', type: Number }) numChips = 3; - - /** - * Flag indicating if overflow chips are visible - */ - @property({ reflect: true, type: Boolean }) open = false; - - /** - * Flag if chip group can be closed - */ - @property({ reflect: true, type: Boolean }) closeable = false; - - @query('#overflow') private _overflowChip?: PfChip; - - @query('#close-button') private _button?: HTMLButtonElement; - - @queryAssignedNodes({ slot: 'category-name', flatten: true }) private _categorySlotted?: Node[]; - - get #chips(): NodeListOf { - return this.querySelectorAll('pf-chip:not([slot]):not([overflow-chip])'); - } - - #tabindex = RovingTabindexController.of(this, { - getItems: () => [ - ...Array.prototype.slice.call(this.#chips, 0, this.open ? this.#chips.length - : Math.min(this.#chips.length, this.numChips)), - this._overflowChip, - this._button, - ].filter(x => !!x), - }); - - /** - * active chip that receives focus when group receives focus - */ - get activeChip() { - const button = this.#tabindex.items.at(this.#tabindex.atFocusedItemIndex); - const shadow = button?.getRootNode() as ShadowRoot; - return shadow?.host as PfChip; - } - - set activeChip(chip: HTMLElement) { - const button = chip.shadowRoot?.querySelector('button'); - if (button) { - this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(button); - } - } - - /** - * whether or not group has a category - */ - get hasCategory(): boolean { - return (this._categorySlotted || []).length > 0; - } - - get remaining(): number { - return this.#chips.length - this.numChips; - } - - constructor() { - super(); - this.addEventListener('remove', this.#onRemove); - } - - render(): TemplateResult<1> { - const empty = this.#chips.length <= 0; - return html` - - `; - } - - /** - * updates chips when they change - */ - @observes('accessibleCloseLabel') - @observes('numChips') - @observes('closeable') - @observes('open') - private chipsChanged(): void { - this.#updateOverflow(); - } - - /** - * handles chip group close - */ - #onCloseClick() { - this.dispatchEvent(new PfChipGroupRemoveEvent()); - } - - /** - * handles overflow chip's click event - * @param event click event - */ - async #onMoreClick(event: Event) { - event.stopPropagation(); - this.open = !this.open; - await this.updateComplete; - this.chipsChanged(); - if (this._overflowChip) { - this.focusOnChip(this._overflowChip); - } - this.dispatchEvent(new PfChipGroupExpandEvent()); - } - - #onSlotchange() { - this.requestUpdate(); - } - - #onRemove(event: Event) { - if (event instanceof PfChipRemoveEvent) { - const index = this.#tabindex.atFocusedItemIndex; - if (event.chip) { - this.#tabindex.atFocusedItemIndex = index + 1; - } - } else if (event instanceof PfChipGroupRemoveEvent) { - this.remove(); - } - } - - /** - * updates which chips are hidden - */ - #updateOverflow() { - this.#chips.forEach((chip, i) => { - chip.accessibleCloseLabel = this.accessibleCloseLabel; - const overflowHidden = i >= this.numChips && !this.open; - chip.hidden = overflowHidden; - }); - } - - /** - * Activates the specified chip and sets focus on it - * @param chip pf-chip element - */ - focusOnChip(chip: PfChip): void { - this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(chip); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-chip-group': PfChipGroup; - } -} diff --git a/elements/pf-chip/pf-chip-shared.css b/elements/pf-chip/pf-chip-shared.css deleted file mode 100644 index 1969522ff6..0000000000 --- a/elements/pf-chip/pf-chip-shared.css +++ /dev/null @@ -1,11 +0,0 @@ -:host { - --pf-c-chip__c-button--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-chip__c-button--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-chip__c-button--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-chip__c-button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-chip__c-button--MarginTop: calc(var(--pf-c-chip--PaddingTop) * -1); - --pf-c-chip__c-button--MarginRight: calc(var(--pf-c-chip--PaddingRight) / 2 * -1); - --pf-c-chip__c-button--MarginBottom: calc(var(--pf-c-chip--PaddingBottom) * -1); - --pf-c-chip__c-button--FontSize: var(--pf-global--FontSize--xs, 0.75rem); - --pf-c-chip__c-badge--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); -} diff --git a/elements/pf-chip/pf-chip.css b/elements/pf-chip/pf-chip.css deleted file mode 100644 index 43485a8a94..0000000000 --- a/elements/pf-chip/pf-chip.css +++ /dev/null @@ -1,129 +0,0 @@ -:host { - /** Top padding for chip */ - --pf-c-chip--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - /** Right padding for chip */ - --pf-c-chip--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - /** Bottom padding for chip */ - --pf-c-chip--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - /** Left padding for chip */ - --pf-c-chip--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - /** Background color for chip */ - --pf-c-chip--BackgroundColor: var(--pf-global--Color--light-100, #fff); - /** Border radius for chip */ - --pf-c-chip--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - /** Border color for chip */ - --pf-c-chip--before--BorderColor: var(--pf-global--BorderColor--300, #f0f0f0); - /** Border width for chip */ - --pf-c-chip--before--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - /** Border radius for chip border (references chip border radius) */ - --pf-c-chip--before--BorderRadius: var(--pf-c-chip--BorderRadius); - /** Text color for overflow chip */ - --pf-c-chip--m-overflow__text--Color: var(--pf-global--primary-color--100, #06c); - /** Background color for draggable chip */ - --pf-c-chip--m-draggable--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - /** Box shadow for draggable chip */ - --pf-c-chip--m-draggable--BoxShadow: var(--pf-global--BoxShadow--sm, 0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06)); - /** Font size for draggable chip icon */ - --pf-c-chip--m-draggable__icon--FontSize: var(--pf-global--icon--FontSize--sm, 0.625rem); - /** Font size for chip text */ - --pf-c-chip__text--FontSize: var(--pf-global--FontSize--xs, 0.75rem); - /** Color for chip text */ - --pf-c-chip__text--Color: var(--pf-global--Color--100, #151515); - /** Maximum width for chip text */ - --pf-c-chip__text--MaxWidth: 16ch; - /** Left margin for chip icon */ - --pf-c-chip__icon--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); - color: var(--pf-global--Color--100, #151515); - position: relative; - display: inline-flex; - align-items: center; - min-width: 0; - list-style: none; - /** Background color of chip */ - background-color: var(--pf-c-chip--BackgroundColor); - /** Border radius of chip */ - border-radius: var(--pf-c-chip--BorderRadius); - padding: - var(--pf-c-chip--PaddingTop) - var(--pf-c-chip--PaddingRight) - var(--pf-c-chip--PaddingBottom) - var(--pf-c-chip--PaddingLeft); -} - -[hidden], -:host([hidden]) { - display: none !important; -} - -div#outer { - display: contents; -} - -#outer:before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - content: ""; - border: - /** Border width */ - var(--pf-c-chip--before--BorderWidth) - solid - /** Border color */ - var(--pf-c-chip--before--BorderColor); - /** Border radius of chip border */ - border-radius: var(--pf-c-chip--before--BorderRadius); -} - -span { - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - position: relative; - /** Maximum width of chip text */ - max-width: var(--pf-c-chip__text--MaxWidth); - /** Font size of chip text */ - font-size: var(--pf-c-chip__text--FontSize); - /** Color of chip text */ - color: var(--pf-c-chip__text--Color); -} - -:host([readonly]) span { - color: var(--pf-global--Color--200, #6a6e73); -} - -/* OVERFLOW */ - -button#outer { - display: flex; - background: none; - border: 1px solid transparent; -} - -button#outer span { - /** Color for overflow chip text */ - color: var(--pf-c-chip--m-overflow__text--Color); -} - -/* CLOSE */ - -#close-button { - --pf-icon--size: 12px; - --pf-c-button--PaddingTop: var(--pf-c-chip__c-button--PaddingTop); - --pf-c-button--PaddingRight: var(--pf-c-chip__c-button--PaddingRight); - --pf-c-button--PaddingBottom: var(--pf-c-chip__c-button--PaddingBottom); - --pf-c-button--PaddingLeft: var(--pf-c-chip__c-button--PaddingLeft); - --pf-c-button--FontSize: var(--pf-c-chip__c-button--FontSize); - margin-top: var(--pf-c-chip__c-button--MarginTop); - margin-right: var(--pf-c-chip__c-button--MarginRight); - margin-bottom: var(--pf-c-chip__c-button--MarginBottom); - inset-block-start: 0.125em; -} - -::slotted(pf-badge) { - font-size: var(--pf-global--FontSize--xs, 12px); - margin: 0 0 0 var(--pf-global--spacer--xs, 0.25rem) !important; - min-width: unset; -} diff --git a/elements/pf-chip/pf-chip.ts b/elements/pf-chip/pf-chip.ts deleted file mode 100644 index 80cc03515f..0000000000 --- a/elements/pf-chip/pf-chip.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import '@patternfly/elements/pf-button/pf-button.js'; - -import styles from './pf-chip.css'; -import shared from './pf-chip-shared.css'; - -export class PfChipRemoveEvent extends Event { - constructor(public chip: PfChip) { - super('remove', { bubbles: true }); - } -} - -/** - * A **chip** is used to communicate a value or a set of attribute-value pairs within workflows that involve filtering a set of objects. - * @alias Chip - * @fires {ChipRemoveEvent} remove - Fires when chip is removed - * @fires {Event} click - when close button is clicked - */ -@customElement('pf-chip') -export class PfChip extends LitElement { - static readonly styles: CSSStyleSheet[] = [shared, styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** - * Accessible label for close button - */ - @property({ attribute: 'accessible-close-label', type: String }) accessibleCloseLabel = 'Close'; - - /** - * Flag indicating if chip is read-only and cannot be removed - */ - @property({ reflect: true, type: Boolean }) readonly = false; - - /** - * Flag indicating if chip is read-only and cannot be removed - */ - @property({ attribute: 'overflow-chip', reflect: true, type: Boolean }) overflowChip = false; - - render(): TemplateResult<1> { - return this.overflowChip ? html` - - ` : html` -
- - - - - - -
- `; - } - - #onClick() { - if (this.dispatchEvent(new PfChipRemoveEvent(this))) { - this.remove(); - } - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-chip': PfChip; - } -} diff --git a/elements/pf-chip/test/pf-chip-group.spec.ts b/elements/pf-chip/test/pf-chip-group.spec.ts deleted file mode 100644 index b844d77a56..0000000000 --- a/elements/pf-chip/test/pf-chip-group.spec.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { expect, html, nextFrame } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { a11ySnapshot, querySnapshot, querySnapshotAll, type A11yTreeSnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; -import { PfChipGroup } from '../pf-chip-group.js'; -import { PfChip } from '../pf-chip.js'; -import { sendKeys } from '@web/test-runner-commands'; - -function press(key: string) { - return async function() { - await sendKeys({ press: key }); - }; -} - -describe('', async function() { - let element: PfChipGroup; - - describe('simply instantiating', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-chip-group')).to.be.an.instanceof(PfChipGroup); - }); - - it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-chip-group'); - expect(element) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfChipGroup); - }); - }); - - describe('with 4 chips', function() { - let element: PfChipGroup; - const updateComplete = () => element.updateComplete; - - beforeEach(async function() { - element = await createFixture(html` - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - `); - }); - - it('displays 3 chips and an overflow button', async function() { - const snapshot = await a11ySnapshot(); - expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(3); - expect(querySnapshotAll(snapshot, { role: 'button' })).to.have.length(4); - }); - - describe('Tab', function() { - beforeEach(press('Tab')); - beforeEach(nextFrame); - it('focuses the first close button', async function() { - const snapshot = await a11ySnapshot(); - const focused = querySnapshot(snapshot, { focused: true }); - expect(focused).to.have.property('name', 'Close'); - expect(focused).to.have.property('description', 'Chip 1'); - }); - describe('ArrowLeft', function() { - beforeEach(press('ArrowLeft')); - it('focuses the show less button', async function() { - const snapshot = await a11ySnapshot(); - const focused = querySnapshot(snapshot, { focused: true }); - expect(focused).to.have.property('name', '1 more'); - }); - describe('Enter', function() { - beforeEach(press('Enter')); - beforeEach(updateComplete); - it('should show all chips', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4); - }); - it('should show collapse button', async function() { - const snapshot = await a11ySnapshot(); - const buttons = querySnapshotAll(snapshot, { role: 'button' }); - expect(buttons).to.have.length(5); - }); - it('should focus collapse button', async function() { - const snapshot = await a11ySnapshot(); - expect(querySnapshot(snapshot, { focused: true })) - .to.have.property('name', 'show less'); - }); - }); - }); - }); - }); - - describe('with 4 chips and `closeable` attribute', function() { - let element: PfChipGroup; - const updateComplete = () => element.updateComplete; - beforeEach(async function() { - element = await createFixture(html` - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - `); - }); - - beforeEach(updateComplete); - it('should have close button', async function() { - const snapshot = await a11ySnapshot(); - const last = snapshot.children?.at(-1); - expect(last?.name).to.equal('Close'); - expect(last?.role).to.equal('button'); - expect(last?.description).to.not.be.ok; - }); - - describe('clicking close button', function() { - beforeEach(() => element.focus()); - beforeEach(press('ArrowLeft')); - beforeEach(press('Enter')); - beforeEach(updateComplete); - it('should remove element', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children).to.not.be.ok; - }); - }); - }); - - describe('with 4 chips and `collapsed-text`, `expanded-text`, and `label` attributes', async function() { - let chip1: PfChip; - let chip2: PfChip; - let element: PfChipGroup; - let snapshot: A11yTreeSnapshot; - const updateComplete = () => element.updateComplete; - - beforeEach(async function() { - element = await createFixture(html` - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - `); - [chip1, chip2] = document.querySelectorAll('pf-chip'); - snapshot = await a11ySnapshot(); - }); - - it('has accessible label', function() { - const [offscreen] = snapshot.children!; - expect(offscreen?.name).to.equal('My Chip Group'); - }); - - it('is accessible', async function() { - await expect(element).to.be.accessible(); - }); - - describe('pressing `Tab`', function() { - beforeEach(press('Tab')); - beforeEach(updateComplete); - it('should focus', function() { - expect(document.activeElement).to.equal(chip1); - }); - }); - - describe('pressing `ArrowRight`', function() { - beforeEach(() => chip1.focus()); - beforeEach(press('ArrowRight')); - beforeEach(updateComplete); - it('should move to chip2', function() { - expect(document.activeElement).to.equal(chip2); - }); - }); - }); - - describe('with 4 chips and `num-chips="2"` attribute', function() { - beforeEach(async function() { - await createFixture(html` - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - `); - }); - it('only 2 chips should be visible', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(2); - }); - }); - - describe('with 4 chips and `num-chips="4"` attribute', function() { - let element: PfChipGroup; - const updateComplete = () => element.updateComplete; - - beforeEach(async function() { - element = await createFixture(html` - - Chip 1 - Chip 2 - Chip 3 - Chip 4 - - `); - }); - - it('all 4 chips should be visible', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4); - }); - - describe('keyboard navigating with arrow keys to third chip and pressing enter', function() { - beforeEach(press('Tab')); - beforeEach(press('ArrowRight')); - beforeEach(press('ArrowRight')); - beforeEach(press('Enter')); - beforeEach(updateComplete); - - it('should remove third chip', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.find(x => x.name === 'Chip 3')).to.not.be.ok; - }); - - it('should focus on close button', async function() { - const snapshot = await a11ySnapshot(); - expect(querySnapshot(snapshot, { focused: true })) - .to.be.ok - .and.have.property('name', 'Close'); - }); - }); - }); -}); diff --git a/elements/pf-clipboard-copy/README.md b/elements/pf-clipboard-copy/README.md deleted file mode 100644 index e8725e5883..0000000000 --- a/elements/pf-clipboard-copy/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Clipboard Copy -Copy-to-clipboard widget - -## Usage - -```html -Content to copy -``` diff --git a/elements/pf-clipboard-copy/demo/code.html b/elements/pf-clipboard-copy/demo/code.html deleted file mode 100644 index f2465cad27..0000000000 --- a/elements/pf-clipboard-copy/demo/code.html +++ /dev/null @@ -1,27 +0,0 @@ -
-

JSON object (pre-formatted code)

- - { "menu": { - "id": "file", - "value": "File", - "popup": { - "menuitem": [ - {"value": "New", "onclick": "CreateNewDoc()"}, - {"value": "Open", "onclick": "OpenDoc()"}, - {"value": "Close", "onclick": "CloseDoc()"} - ] - } - }} - -
- - - - diff --git a/elements/pf-clipboard-copy/demo/expandable.html b/elements/pf-clipboard-copy/demo/expandable.html deleted file mode 100644 index 8f30a89bb6..0000000000 --- a/elements/pf-clipboard-copy/demo/expandable.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - Got a lot of text here, need to see all of it? - Click that arrow on the left side and check out the resulting expansion. - -
- - - - diff --git a/elements/pf-clipboard-copy/demo/index.html b/elements/pf-clipboard-copy/demo/index.html deleted file mode 100644 index 695cf422f8..0000000000 --- a/elements/pf-clipboard-copy/demo/index.html +++ /dev/null @@ -1,14 +0,0 @@ -
- -
- - - - diff --git a/elements/pf-clipboard-copy/demo/inline-compact-code.html b/elements/pf-clipboard-copy/demo/inline-compact-code.html deleted file mode 100644 index ac84d06ed3..0000000000 --- a/elements/pf-clipboard-copy/demo/inline-compact-code.html +++ /dev/null @@ -1,14 +0,0 @@ -
- 2.3.4-2-redhat -
- - - - diff --git a/elements/pf-clipboard-copy/demo/inline-compact.html b/elements/pf-clipboard-copy/demo/inline-compact.html deleted file mode 100644 index 0bad2390b0..0000000000 --- a/elements/pf-clipboard-copy/demo/inline-compact.html +++ /dev/null @@ -1,32 +0,0 @@ -
- 2.3.4-2-redhat -

Inline compact with additional action

- 2.3.4-2-redhat - - - - -
-

Inline compact in sentence

-

Basic

-
-

Lorem ipsum 2.3.4-2-redhat

- -

Long copy string

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, ullamcorper pharetra lacus nibh vitae enim.

- -

Long copy string in block

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. - https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, ullamcorper pharetra lacus nibh vitae enim.

-
- - - - diff --git a/elements/pf-clipboard-copy/demo/read-only-expandable.html b/elements/pf-clipboard-copy/demo/read-only-expandable.html deleted file mode 100644 index 09d7e20e50..0000000000 --- a/elements/pf-clipboard-copy/demo/read-only-expandable.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - Got a lot of text here, need to see all of it? - Click that arrow on the left side and check out the resulting expansion. - -
- - - - diff --git a/elements/pf-clipboard-copy/demo/read-only-expanded.html b/elements/pf-clipboard-copy/demo/read-only-expanded.html deleted file mode 100644 index 5fb7ed09e8..0000000000 --- a/elements/pf-clipboard-copy/demo/read-only-expanded.html +++ /dev/null @@ -1,17 +0,0 @@ -
- -

Got a lot of text here, need to see all of it? Click that arrow on the left side and check out the resulting expansion.

-

asodifna osdif

-
-
- - - - diff --git a/elements/pf-clipboard-copy/demo/read-only.html b/elements/pf-clipboard-copy/demo/read-only.html deleted file mode 100644 index 8fc02f699e..0000000000 --- a/elements/pf-clipboard-copy/demo/read-only.html +++ /dev/null @@ -1,14 +0,0 @@ -
- -
- - - - diff --git a/elements/pf-clipboard-copy/docs/pf-clipboard-copy.md b/elements/pf-clipboard-copy/docs/pf-clipboard-copy.md deleted file mode 100644 index f05c0d260e..0000000000 --- a/elements/pf-clipboard-copy/docs/pf-clipboard-copy.md +++ /dev/null @@ -1,106 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - -{% endrenderOverview %} - -{% band header="Usage" %} - ### Read-only - {% htmlexample %} - - {% endhtmlexample %} - - ### Expanded - {% htmlexample %} - - Got a lot of text here, need to see all of it? - Click that arrow on the left side and check out the resulting expansion. - - {% endhtmlexample %} - - ### Read-only expanded - {% htmlexample %} - - Got a lot of text here, need to see all of it? - Click that arrow on the left side and check out the resulting expansion. - - {% endhtmlexample %} - - ### Read-only expanded by default - {% htmlexample %} - -

Got a lot of text here, need to see all of it? Click that arrow on the left side and check out the resulting expansion.

-

asodifna osdif

-
- {% endhtmlexample %} - - ### JSON object (pre-formatted code) - {% htmlexample %} - - { "menu": { - "id": "file", - "value": "File", - "popup": { - "menuitem": [ - {"value": "New", "onclick": "CreateNewDoc()"}, - {"value": "Open", "onclick": "OpenDoc()"}, - {"value": "Close", "onclick": "CloseDoc()"} - ] - } - }} - - {% endhtmlexample %} - - ### Inline compact - {% htmlexample %} - 2.3.4-2-redhat - {% endhtmlexample %} - - ### Inline compact code - {% htmlexample %} - 2.3.4-2-redhat - {% endhtmlexample %} - - ### Inline compact with additional action - {% htmlexample %} - 2.3.4-2-redhat - - - - - {% endhtmlexample %} - - ### Inline compact in sentence - #### Basic - {% htmlexample %} -

Lorem ipsum 2.3.4-2-redhat

- {% endhtmlexample %} - - #### Long copy string - {% htmlexample %} -

Lorem ipsum dolor sit amet, consectetur adipiscing elit. https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 - Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, - ullamcorper pharetra lacus nibh vitae enim.

- {% endhtmlexample %} - - #### Long copy string in block - {% htmlexample %} -

Lorem ipsum dolor sit amet, consectetur adipiscing elit. - https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 - Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, - ullamcorper pharetra lacus nibh vitae enim. -

- {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-clipboard-copy/pf-clipboard-copy.css b/elements/pf-clipboard-copy/pf-clipboard-copy.css deleted file mode 100644 index a0f674eb5f..0000000000 --- a/elements/pf-clipboard-copy/pf-clipboard-copy.css +++ /dev/null @@ -1,187 +0,0 @@ -:host { - /** Transition for the toggle icon */ - --pf-c-clipboard-copy__toggle-icon--Transition: .2s ease-in 0s; - /** Rotate value for the toggle icon when expanded */ - --pf-c-clipboard-copy--m-expanded__toggle-icon--Rotate: 90deg; - /** PaddingTop for the expandable content */ - --pf-c-clipboard-copy__expandable-content--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** PaddingRight for the expandable content */ - --pf-c-clipboard-copy__expandable-content--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** PaddingBottom for the expandable content */ - --pf-c-clipboard-copy__expandable-content--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** PaddingLeft for the expandable content */ - --pf-c-clipboard-copy__expandable-content--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** BackgroundColor for the expandable content */ - --pf-c-clipboard-copy__expandable-content--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #fff); - /** BorderTopWidth for the expandable content */ - --pf-c-clipboard-copy__expandable-content--BorderTopWidth: 0; - /** BorderRightWidth for the expandable content */ - --pf-c-clipboard-copy__expandable-content--BorderRightWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderBottomWidth for the expandable content */ - --pf-c-clipboard-copy__expandable-content--BorderBottomWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderLeftWidth for the expandable content */ - --pf-c-clipboard-copy__expandable-content--BorderLeftWidth: var(--pf-global--BorderWidth--sm, 1px); - /** BorderColor for the expandable content */ - --pf-c-clipboard-copy__expandable-content--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** OutlineOffset for the expandable content */ - --pf-c-clipboard-copy__expandable-content--OutlineOffset: calc(-1 * var(--pf-global--spacer--xs, 0.25rem)); - /** PaddingTop for the inline variant */ - --pf-c-clipboard-copy--m-inline--PaddingTop: 0; - /** PaddingBottom for the inline variant */ - --pf-c-clipboard-copy--m-inline--PaddingBottom: 0; - /** PaddingLeft for the inline variant */ - --pf-c-clipboard-copy--m-inline--PaddingLeft: var(--pf-global--spacer--xs, 0.25rem); - /** BackgroundColor for the inline variant */ - --pf-c-clipboard-copy--m-inline--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - /** FontFamily for the code text */ - --pf-c-clipboard-copy__text--m-code--FontFamily: var(--pf-global--FontFamily--monospace, "Liberation Mono", consolas, "SFMono-Regular", menlo, monaco, "Courier New", monospace); - /** FontSize for the code text */ - --pf-c-clipboard-copy__text--m-code--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** MarginTop for actions item */ - --pf-c-clipboard-copy__actions-item--MarginTop: calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)); - /** MarginBottom for actions item */ - --pf-c-clipboard-copy__actions-item--MarginBottom: calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)); - /** PaddingTop for actions item button */ - --pf-c-clipboard-copy__actions-item--button--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - /** PaddingRight for actions item button */ - --pf-c-clipboard-copy__actions-item--button--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - /** PaddingBottom for actions item button */ - --pf-c-clipboard-copy__actions-item--button--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - /** PaddingLeft for actions item button */ - --pf-c-clipboard-copy__actions-item--button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-icon--size: var(--pf-global--FontSize--md, 1rem); -} - -[hidden], -[inert], -[inert]::slotted(*) { - display: none !important; -} - -#container { - flex-direction: column; -} - -#container, -#input-group, -#wrapper { - display: flex; -} - -.inline #wrapper { - display: inline-flex; -} - -#input-group > * + * { - margin-left: -1px; -} - -input { - color: var(--pf-c-form-control--Color); - width: var(--pf-c-form-control--Width); - padding: - var(--pf-c-form-control--PaddingTop) - var(--pf-c-form-control--PaddingRight) - var(--pf-c-form-control--PaddingBottom) - var(--pf-c-form-control--PaddingLeft); - font-size: var(--pf-c-form-control--FontSize); - line-height: var(--pf-c-form-control--LineHeight); - background-color: var(--pf-c-form-control--BackgroundColor); - background-repeat: no-repeat; - border: var(--pf-c-form-control--BorderWidth) solid; - border-color: - var(--pf-c-form-control--BorderTopColor) - var(--pf-c-form-control--BorderRightColor) - var(--pf-c-form-control--BorderBottomColor) - var(--pf-c-form-control--BorderLeftColor); - border-radius: var(--pf-c-form-control--BorderRadius); - margin: 0; - appearance: none; - height: var(--pf-c-form-control--Height); - text-overflow: ellipsis; - flex: 1 1 auto; -} - -input[disabled] { - background-color: var(--pf-c-form-control--readonly--BackgroundColor); -} - -textarea { - display: flex; - flex: 1 1 auto; - padding: - var(--pf-c-clipboard-copy__expandable-content--PaddingTop) - var(--pf-c-clipboard-copy__expandable-content--PaddingRight) - var(--pf-c-clipboard-copy__expandable-content--PaddingBottom) - var(--pf-c-clipboard-copy__expandable-content--PaddingLeft); - word-wrap: break-word; - background-color: var(--pf-c-clipboard-copy__expandable-content--BackgroundColor); - background-clip: padding-box; - border: solid var(--pf-c-clipboard-copy__expandable-content--BorderColor); - border-width: - var(--pf-c-clipboard-copy__expandable-content--BorderTopWidth) - var(--pf-c-clipboard-copy__expandable-content--BorderRightWidth) - var(--pf-c-clipboard-copy__expandable-content--BorderBottomWidth) - var(--pf-c-clipboard-copy__expandable-content--BorderLeftWidth); - box-shadow: var(--pf-c-clipboard-copy__expandable-content--BoxShadow); - margin: 0; - color: inherit; - font-family: inherit; -} - -#input-group { - display: flex; - height: 100%; -} - -#container.code textarea { - font-family: var(--pf-global--FontFamily--monospace, - var(--pf-global--FontFamily--redhat--monospace, - "RedHatMono", - "Liberation Mono", - consolas, - "SFMono-Regular", - menlo, - monaco, - "Courier New", - monospace)); -} - -#container.expanded #expand-button pf-icon { - rotate: 90deg; -} - -#container.inline { - display: inline; - background-color: var(--pf-c-clipboard-copy--m-inline--BackgroundColor); - padding-block-start: var(--pf-c-clipboard-copy--m-inline--PaddingTop); - padding-block-end: var(--pf-c-clipboard-copy--m-inline--PaddingBottom); - padding-inline-start: var(--pf-c-clipboard-copy--m-inline--PaddingLeft); - word-break: break-word; - white-space: normal; -} - -#container:is(.compact, .inline) #input-group { - display: contents; -} - -#container:is(.compact, .inline) #input-group { - background-color: var(--pf-c-button--m-plain--BackgroundColor, - var(--pf-global--BackgroundColor--200, #f0f0f0)); -} - -#container.compact.block { - display: block; - background-color: var(--pf-c-clipboard-copy--m-inline--BackgroundColor); -} - -#container:is(.compact, .inline) #copy-button, -#container:is(.compact, .inline) slot[name="actions"]::slotted(*) { - --pf-c-button--PaddingTop: var(--pf-c-clipboard-copy__actions-item--button--PaddingTop) !important; - --pf-c-button--PaddingRight: var(--pf-c-clipboard-copy__actions-item--button--PaddingRight) !important; - --pf-c-button--PaddingBottom: var(--pf-c-clipboard-copy__actions-item--button--PaddingBottom) !important; - --pf-c-button--PaddingLeft: var(--pf-c-clipboard-copy__actions-item--button--PaddingLeft) !important; - margin-block-start: calc(-1 * var(--pf-c-button--PaddingTop)); - margin-block-end: calc(-1 * var(--pf-c-button--PaddingBottom)); -} - diff --git a/elements/pf-clipboard-copy/pf-clipboard-copy.ts b/elements/pf-clipboard-copy/pf-clipboard-copy.ts deleted file mode 100644 index 52f6b957ab..0000000000 --- a/elements/pf-clipboard-copy/pf-clipboard-copy.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { LitElement, html, isServer, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; - -import styles from './pf-clipboard-copy.css'; -import formControlStyles from '../form-control.css'; - -import '@patternfly/elements/pf-button/pf-button.js'; -import '@patternfly/elements/pf-icon/pf-icon.js'; -import '@patternfly/elements/pf-tooltip/pf-tooltip.js'; - -const sleep = (ms?: number) => new Promise(r => setTimeout(r, ms)); - -export class PfClipboardCopyCopiedEvent extends Event { - constructor(public text: string) { - super('copy', { bubbles: true }); - } -} - -/** - * The **clipboard copy** component allows users to quickly and easily copy content to their clipboard. - * @alias Clipboard Copy - * @fires {PfClipboardCopyCopiedEvent} copy - when the text snippet is successfully copied. - */ -@customElement('pf-clipboard-copy') -export class PfClipboardCopy extends LitElement { - static readonly styles: CSSStyleSheet[] = [formControlStyles, styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** Tooltip message to display when clicking the copy button */ - @property({ attribute: 'click-tip' }) clickTip = 'Successfully copied to clipboard!'; - - /** Tooltip message to display when hover the copy button */ - @property({ attribute: 'hover-tip' }) hoverTip = 'Copy to clipboard'; - - /** Accessible label to use on the text input. */ - @property({ attribute: 'accessible-text-label' }) accessibleTextLabel = 'Copyable input'; - - /** Accessible label to use on the toggle button. */ - @property({ attribute: 'accessible-toggle-label' }) accessibleToggleLabel = 'Show content'; - - /** Delay in ms before the tooltip appears. */ - @property({ type: Number, attribute: 'entry-delay' }) entryDelay = 300; - - /** Delay in ms before the tooltip disappears. */ - @property({ type: Number, attribute: 'exit-delay' }) exitDelay = 1500; - - /** Flag to determine if inline clipboard copy should be block styling */ - @property({ type: Boolean, reflect: true }) block = false; - - /** Flag to determine if clipboard copy content includes code */ - @property({ type: Boolean, reflect: true }) code = false; - - /** Flag to determine if clipboard copy is in the expanded state */ - @property({ type: Boolean, reflect: true }) expanded = false; - - /** Implies not `inline`. */ - @property({ type: Boolean, reflect: true }) expandable = false; - - /** Flag to show if the input is read only. */ - @property({ type: Boolean, reflect: true }) readonly = false; - - /** Implies not expandable. Overrules `expandable`. */ - @property({ type: Boolean, reflect: true }) inline = false; - - /** Flag to determine if inline clipboard copy should have compact styling */ - @property({ type: Boolean, reflect: true }) compact = false; - - /** String to copy */ - @property() value = ''; - - #copied = false; - - #mo = new MutationObserver(() => this.#onMutation()); - - connectedCallback(): void { - super.connectedCallback(); - this.#mo.observe(this, { characterData: true }); - if (!isServer) { - this.#onMutation(); - } - } - - /** - * @todo fix the collapsed whitespace between the end of the "inline-compact" variant and the text node. - * This demonstrates the collapsed whitespace issue. - * The extra space between the closing slot tag and the closing template literal results in a collapsed whitespace. - */ - render(): TemplateResult<1> { - const { expanded, expandable, inline, compact, code, block, readonly } = this; - return html` -
-
-
- - - -
- ${this.value} - - - - - ${this.#copied ? this.clickTip : this.hoverTip} - - - -
- -
- `; - } - - #onClick() { - this.expanded = !this.expanded; - } - - #onChange(e: Event) { - const { value } = e.target as HTMLInputElement || HTMLTextAreaElement; - this.value = value; - } - - #onMutation() { - if (this.childNodes?.length > 0) { - this.value = this.getAttribute('value') ?? this.#dedent(Array.from(this.childNodes, child => - (child instanceof Element || child instanceof Text) ? (child.textContent ?? '') : '') - .join('')); - } - } - - #dedent(str: string): string { - const stripped = str.replace(/^\n/, ''); - const match = stripped.match(/^\s+/); - return match ? stripped.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str; - } - - /** - * Copy the current value to the clipboard. - */ - async copy(): Promise { - await navigator.clipboard.writeText(this.value); - this.dispatchEvent(new PfClipboardCopyCopiedEvent(this.value)); - await sleep(this.entryDelay); - this.#copied = true; - this.requestUpdate(); - await sleep(this.exitDelay); - this.#copied = false; - this.requestUpdate(); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-clipboard-copy': PfClipboardCopy; - } -} diff --git a/elements/pf-code-block/README.md b/elements/pf-code-block/README.md deleted file mode 100644 index 83c2ac1fed..0000000000 --- a/elements/pf-code-block/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# PatternFly Elements Code Block -`` is a component that contains two or more lines of ready-only -code. The code in a code block can be copied to the clipboard. - -Read more about Code Block in the [PatternFly Elements Code Block -documentation](https://patternflyelements.org/components/code-block) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/pf-code-block -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/pf-code-block'; -``` - -## Usage -### Basic - -Place your code in a `script` tag with a [non-javascript mimetype][mime]. -JavaScript snippets **must** use the `text/javascript-sample` script type. -Script text content will be automatically dedented. - - -To add copy-to-clipboard functionality, be sure to import -`@patternfly/pf-clipboard` and add the `pf-clipboard` to the `action` slot. - -```html - - - -``` - -### Expandable - -If a block of code is long and you'd like to hide some of the code to take up -less height on the page, place the code you'd like to hide in a `script` with -`data-expand` attribute present. - -It is important that you place the span right next to the first `script`, -because of how the whitespace will be handled. Failing to do this can result in -unwanted line breaks. - -```html - - - - -``` diff --git a/elements/pf-code-block/demo/expandable.html b/elements/pf-code-block/demo/expandable.html deleted file mode 100644 index d53b405681..0000000000 --- a/elements/pf-code-block/demo/expandable.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - - - -
- - - - diff --git a/elements/pf-code-block/demo/expanded.html b/elements/pf-code-block/demo/expanded.html deleted file mode 100644 index 389c655c61..0000000000 --- a/elements/pf-code-block/demo/expanded.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - - - -
- - - - diff --git a/elements/pf-code-block/demo/index.html b/elements/pf-code-block/demo/index.html deleted file mode 100644 index 47990c0195..0000000000 --- a/elements/pf-code-block/demo/index.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - - - -
- - - - diff --git a/elements/pf-code-block/demo/javascript-sample.html b/elements/pf-code-block/demo/javascript-sample.html deleted file mode 100644 index 3fa40eeccd..0000000000 --- a/elements/pf-code-block/demo/javascript-sample.html +++ /dev/null @@ -1,20 +0,0 @@ -
-

JavaScript snippets must use the text/javascript-sample script type.

- - - - -
- - - - diff --git a/elements/pf-code-block/demo/noscript.html b/elements/pf-code-block/demo/noscript.html deleted file mode 100644 index 558b68509b..0000000000 --- a/elements/pf-code-block/demo/noscript.html +++ /dev/null @@ -1,51 +0,0 @@ -
-

To display code snippets when javascript is disabled, use the following HTML snippet:

- - - - - - -
- - - - diff --git a/elements/pf-code-block/docs/pf-code-block.md b/elements/pf-code-block/docs/pf-code-block.md deleted file mode 100644 index 22a84af138..0000000000 --- a/elements/pf-code-block/docs/pf-code-block.md +++ /dev/null @@ -1,80 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - Code Block is a component that contains two or more lines of read-only code. The code in a code block can be copied to the clipboard. - - - - - -{% endrenderOverview %} - -{% band header="Usage" %} -### Basic - -Place your code in a `script` tag with a [non-javascript mimetype][mime]. -JavaScript snippets **must** use the `text/javascript-sample` script type. -Script text content will be automatically dedented. - -To add copy-to-clipboard functionality, be sure to import `@patternfly/pf-clipboard` and add the `pf-clipboard` to the `action` slot. - -{% htmlexample %} - - - - -{% endhtmlexample %} - -### Expandable - -If a block of code is long and you'd like to hide some of the code to take us less height on the page, place the code you'd like to hide in a `span` with `slot="expandable-code"`. - -It is important that you place the span right next to the ` - -
-{% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} - -[mime]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#textjavascript diff --git a/elements/pf-code-block/pf-code-block.css b/elements/pf-code-block/pf-code-block.css deleted file mode 100644 index 45dd598151..0000000000 --- a/elements/pf-code-block/pf-code-block.css +++ /dev/null @@ -1,66 +0,0 @@ -:host { - display: block; - /** Background color for code block */ - background-color: var(--pf-c-code-block--BackgroundColor, #f0f0f0); - /** Font size for code block pre element */ - font-size: var(--pf-c-code-block__pre--FontSize, 0.875rem); - /** Font family for code block pre element */ - font-family: var(--pf-c-code-block__pre--FontFamily, "Liberation Mono", consolas, "SFMono-Regular", menlo, monaco, "Courier New", monospace); -} - -[hidden] { - display: none !important; -} - -#container { - margin: 0; - /** Top padding for code block content */ - padding-top: var(--pf-c-code-block__content--PaddingTop, 1rem); - /** Right padding for code block content */ - padding-right: var(--pf-c-code-block__content--PaddingRight, 1rem); - /** Bottom padding for code block content */ - padding-bottom: var(--pf-c-code-block__content--PaddingBottom, 1rem); - /** Left padding for code block content */ - padding-left: var(--pf-c-code-block__content--PaddingLeft, 1rem); -} - -#header { - /** Border bottom width for code block header */ - border-bottom: var(--pf-c-code-block__header--BorderBottomWidth, 1px) solid - /** Border bottom color for code block header */ - var(--pf-c-code-block__header--BorderBottomColor, #d2d2d2); -} - -pre { - margin: 0; -} - -slot[name="actions"] { - display: flex; - justify-content: end; -} - -#expanded { - display: inline; -} - -button { - display: flex; - background: none; - border: none; - padding: 6px 16px 6px 0; - color: #06c; - cursor: pointer; - font-size: 16px; -} - -button svg { - color: #151515; - margin-right: 12px; - transition: .2s ease-in 0s; - vertical-align: -0.125em; -} - -.expanded button svg { - transform: rotate(-90deg); -} diff --git a/elements/pf-code-block/pf-code-block.ts b/elements/pf-code-block/pf-code-block.ts deleted file mode 100644 index aa9fe83473..0000000000 --- a/elements/pf-code-block/pf-code-block.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; -import styles from './pf-code-block.css'; - -/** - * A **code block** is a component that contains 2 or more lines of read-only code. The code in a code block can be copied to the clipboard. - * @alias Code Block - * @attr {boolean} [expanded=false] - * Indicates if the code-block has been expanded - */ - -function dedent(str: string): string { - const stripped = str.replace(/^\n/, ''); - const match = stripped.match(/^\s+/); - return match ? stripped.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str; -} - -@customElement('pf-code-block') -export class PfCodeBlock extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** Flag for whether the code block is expanded */ - @property({ type: Boolean, reflect: true }) expanded = false; - - get #expandedContent(): string { - return this.querySelector?.('script[data-expand]')?.textContent ?? ''; - } - - get #content() { - const script = this.querySelector?.('script[type]'); - if (script?.type !== 'text/javascript-sample' - && !!script?.type.match(/(j(ava)?|ecma|live)script/)) { - return ''; - } else { - return dedent(script?.textContent ?? ''); - } - } - - override render(): TemplateResult<1> { - const { expanded } = this; - return html` - -
-
${this.#content}${this.#expandedContent}
- -
- `; - } - - #toggle() { - this.expanded = !this.expanded; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-code-block': PfCodeBlock; - } -} diff --git a/elements/pf-code-block/test/pf-code-block.spec.ts b/elements/pf-code-block/test/pf-code-block.spec.ts deleted file mode 100644 index c51a4c952f..0000000000 --- a/elements/pf-code-block/test/pf-code-block.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { expect, html, nextFrame } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfCodeBlock } from '@patternfly/elements/pf-code-block/pf-code-block.js'; - -const element = html` - - - - -`; - -const expandElement = html` - - - - -`; - -const expandElementByDefault = html` - - - - -`; - -describe('', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-code-block')).to.be.an.instanceof(PfCodeBlock); - }); - - it('should upgrade', async function() { - const el = await createFixture (element); - const klass = customElements.get('pf-code-block'); - expect(el) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfCodeBlock); - }); - - it('should not show a "Show more" button by default', async function() { - const el = await createFixture (element); - const button = el.shadowRoot?.querySelector('button'); - expect(button?.hidden).to.be.true; - }); - - it('should show a "Show more" button if content is present in the expandable-code slot', async function() { - const expandEl = await createFixture(expandElement); - const expandButton = expandEl.shadowRoot?.querySelector('button'); - expect(expandButton).to.not.be.null; - }); - - it('should expand the code block when the "Show more" button is clicked', async function() { - const expandEl = await createFixture(expandElement); - const expandButton = expandEl.shadowRoot?.querySelector('button'); - expandButton?.click(); - await nextFrame(); - expect(expandEl.hasAttribute('expanded')).to.be.true; - expect(expandButton?.getAttribute('aria-expanded')).to.equal('true'); - }); - - it('should toggle the "Show more" button text to "Show less" when the button is clicked', async function() { - const expandEl = await createFixture(expandElement); - const expandButton = expandEl.shadowRoot?.querySelector('button'); - expandButton?.click(); - await nextFrame(); - expect(expandButton?.textContent?.trim()).to.equal('Show less'); - }); - - it('should be expanded by default if the expanded attribute is present on render', async function() { - const expandByDefaultEl = await createFixture(expandElementByDefault); - const expandButton = expandByDefaultEl.shadowRoot?.querySelector('button'); - expect(expandButton?.textContent?.trim()).to.equal('Show less'); - }); -}); diff --git a/elements/pf-dropdown/README.md b/elements/pf-dropdown/README.md deleted file mode 100644 index 2dba82d5f5..0000000000 --- a/elements/pf-dropdown/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Dropdown - -A dropdown presents a menu of actions or links in a constrained space that will -trigger a process or navigate to a new location. - -Read more about dropdown in the [PatternFly Elements Dropdown -documentation](https://patternflyelements.org/components/dropdown) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-dropdown/pf-dropdown.js'; -``` - -## Usage - -```html - - item4 -
- - item1 - item2 -
- item3 -
- - item1 - item2 - -
-``` diff --git a/elements/pf-dropdown/context.ts b/elements/pf-dropdown/context.ts deleted file mode 100644 index f3f0c6e0ad..0000000000 --- a/elements/pf-dropdown/context.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Context } from '@lit/context'; -import { createContextWithRoot } from '@patternfly/pfe-core/functions/context.js'; - -export interface PfDropdownContext { - disabled: boolean; -} - -export const context: Context = - createContextWithRoot(Symbol('pf-dropdown-menu-context')); diff --git a/elements/pf-dropdown/demo/custom-toggle.html b/elements/pf-dropdown/demo/custom-toggle.html deleted file mode 100644 index 9cd994dc54..0000000000 --- a/elements/pf-dropdown/demo/custom-toggle.html +++ /dev/null @@ -1,131 +0,0 @@ -

- - Note: When using a custom toggle, you must slot in a pf-dropdown-menu. - - - - - -

- - - - Control toggle - - - - item4 -
- - item1 - item2 -
- item3 -
- - item1 - item2 - item3 - disabled - -
-
- - - Primary toggle - - item4 -
- - item1 - item2 -
- item3 -
- - item1 - item2 - item3 - disabled - -
-
- - - Secondary toggle - - item4 -
- - item1 - item2 -
- item3 -
- - item1 - item2 - item3 - disabled - -
-
- - - Plain toggle - - item4 -
- - item1 - item2 -
- item3 -
- - item1 - item2 - item3 - disabled - -
-
- - - - - item4 -
- - item1 - item2 -
- item3 -
- - item1 - item2 - item3 - disabled - -
-
- - - - - diff --git a/elements/pf-dropdown/demo/descriptions.html b/elements/pf-dropdown/demo/descriptions.html deleted file mode 100644 index e1853e2f78..0000000000 --- a/elements/pf-dropdown/demo/descriptions.html +++ /dev/null @@ -1,11 +0,0 @@ - - item1 - item2le deuxième élément - item3 - item4 - - - - diff --git a/elements/pf-dropdown/demo/disabled-item.html b/elements/pf-dropdown/demo/disabled-item.html deleted file mode 100644 index dba8078107..0000000000 --- a/elements/pf-dropdown/demo/disabled-item.html +++ /dev/null @@ -1,17 +0,0 @@ - - Toggle dropdown - item4 -
- - item1 - item2 -
- item3 -
- disabled item -
- - - diff --git a/elements/pf-dropdown/demo/disabled.html b/elements/pf-dropdown/demo/disabled.html deleted file mode 100644 index b2b3b13612..0000000000 --- a/elements/pf-dropdown/demo/disabled.html +++ /dev/null @@ -1,15 +0,0 @@ - - item1 - item2 - nav item - - -

Interactive content behind the menu should not be activated when a disabled menu item is clicked.

-
Should not toggle behind dropdown -

OOPS! I did it again

-
- - - diff --git a/elements/pf-dropdown/demo/groups.html b/elements/pf-dropdown/demo/groups.html deleted file mode 100644 index 91dfb2dd81..0000000000 --- a/elements/pf-dropdown/demo/groups.html +++ /dev/null @@ -1,19 +0,0 @@ - - item4 -
- - item1 - item2 -
- item3 -
- - item1 - item2 - -
- - - diff --git a/elements/pf-dropdown/demo/index.html b/elements/pf-dropdown/demo/index.html deleted file mode 100644 index 0df79fae1b..0000000000 --- a/elements/pf-dropdown/demo/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - item1 - item2 - item3 - - - - diff --git a/elements/pf-dropdown/docs/pf-dropdown.md b/elements/pf-dropdown/docs/pf-dropdown.md deleted file mode 100644 index 4ac1866281..0000000000 --- a/elements/pf-dropdown/docs/pf-dropdown.md +++ /dev/null @@ -1,220 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - - - -{% renderOverview %} - - Action - Link - Disabled Action - Link -
- Separated Action -
- Separated Link -
-{% endrenderOverview %} - -{% band header="Usage" %} - -### Basic - -The following example shows a few different states of dropdown items. As shown in this example, dropdown items may: - -- Use the `disabled` property to disable an item. -- Use the `href` property to make the item a link. - -This example also uses an `hr` to split the menu into 2 sections with a horizontal line. - -{% htmlexample %} - - Action - Link - Disabled Action - Link -
- Separated Action - Separated Link -
-{% endhtmlexample %} - -### Custom toggle - -A custom toggle can be added using the `toggle` slot. - -{% htmlexample %} - - - My Custom Dropdown - - - Action - Link - Disabled Action - Link -
- Separated Action - Separated Link -
-{% endhtmlexample %} - -### With kebab toggle - -When there isn't enough space for a labeled button, a slotted toggle button with a kebab icon (three dots) can be used to toggle the dropdown menu open or closed. Make sure to add an `aria-label` to the toggle so that assistive technology users know what the button is. - -{% htmlexample %} - - - - - Action - Link - Disabled Action - Link -
- Separated Action - Separated Link -
-{% endhtmlexample %} - -### With groups of items - -To group sets of related dropdown items together, use a `pf-dropdown-group`. When more than 1 `pf-dropdown-group` is created in a menu, use the label and labelHeadingLevel properties to assign a name and heading level to each group. - -{% htmlexample %} - - - Action - Link - -
- - Group 2 action - Group 2 link - -
- - Group 3 action - Group 3 link - -
-{% endhtmlexample %} - -### With item descriptions - -To provide users with more context about a `pf-dropdown-item`, pass a short message to `description` slot. As shown in the example below, an item's description will appear below its label. - -{% htmlexample %} - - - Action - This is a description - - - Link - This is a very long description that describes the menu item - - - Disabled action - Disabled action description - - - Disabled link - Disabled link description - - -{% endhtmlexample %} - -### Dropdown with icon - -Use the `icon` slot to add an icon. - -{% htmlexample %} - - - - User - - - - Lock - - -{% endhtmlexample %} - -### Disabled dropdown - -Use the `disabled` attribute to disable an entire dropdown. - -{% htmlexample %} - - Action - Link - -{% endhtmlexample %} - -{% endband %} - -{% band header="Accessibility" %} - -The dropdown uses the [Menu Button Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) recommendations from the WAI ARIA [Authoring Best Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg). - -{% renderFile "./docs/_snippets/wai-aria-disabled.md" %} - -#### Menu button - -When focus is on the menu button, the following keyboard interactions apply: - -| Key | Function | -| --------------------- | ------------------------------------------------------------------------------------- | -| Enter | Opens the menu. | -| Space | Opens the menu. | -| Down arrow | Opens the menu and moves focus to the first menu item. | -| Tab | Moves focus out of dropdown element onto the next focusable item and closes menu. | -| Shift+Tab | Moves focus out of dropdown element onto the previous focusable item and closes menu. | - -#### Menu items - -Menu items use the [APG's Roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) recommendation. When focus is on the menu items, the following keyboard interactions apply: - -| Key | Function | -| ---------------------- | --------------------------------------------------------------------------------- | -| Enter | Activates the item and closes the menu. | -| Space | Activates the item and closes the menu. | -| Tab | Moves focus out of dropdown element onto the next focusable item and closes menu. | -| Shift+Tab | Moves focus to the menu button and closes menu. | -| Up Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | -| Down Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | -| Left Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | -| Right Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | -| Home | Moves focus to the first item in the current menu. | -| End | Moves focus to the last item in the current menu. | -| Escape | Close the menu that contains focus and return focus to the menu button. | - -{% endband %} - -{% renderSlots %}{% endrenderSlots %} -{% renderSlots for="pf-dropdown-item", level=3, header="Slots on `pf-dropdown-item`" %}{% endrenderSlots %} -{% renderSlots for="pf-dropdown-group", level=3, header="Slots on `pf-dropdown-group`" %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} -{% renderAttributes for="pf-dropdown-item", level=3, header="Slots on `pf-dropdown-item`" %}{% endrenderAttributes %} -{% renderAttributes for="pf-dropdown-group", level=3, header="Slots on `pf-dropdown-group`" %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} -{% renderMethods for="pf-dropdown-item", level=3, header="Slots on `pf-dropdown-item`" %}{% endrenderMethods %} -{% renderMethods for="pf-dropdown-group", level=3, header="Slots on `pf-dropdown-group`" %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} -{% renderEvents for="pf-dropdown-item", level=3, header="Slots on `pf-dropdown-item`" %}{% endrenderEvents %} -{% renderEvents for="pf-dropdown-group", level=3, header="Slots on `pf-dropdown-group`" %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} -{% renderCssCustomProperties for="pf-dropdown-item", level=3, header="CSS Properties on `pf-dropdown-item`" %}{% endrenderCssCustomProperties %} -{% renderCssCustomProperties for="pf-dropdown-group", level=3, header="CSS Properties on `pf-dropdown-group`" %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} -{% renderCssParts for="pf-dropdown-item", level=3, header="Parts on `pf-dropdown-item`" %}{% endrenderCssParts %} -{% renderCssParts for="pf-dropdown-group", level=3, header="Parts on `pf-dropdown-group`" %}{% endrenderCssParts %} diff --git a/elements/pf-dropdown/pf-dropdown-group.css b/elements/pf-dropdown/pf-dropdown-group.css deleted file mode 100644 index 2b45ab5bc3..0000000000 --- a/elements/pf-dropdown/pf-dropdown-group.css +++ /dev/null @@ -1,38 +0,0 @@ -:host { - display: block; -} - -[hidden] { - display: none !important; -} - -p { - margin: 0; - font-size: 14px; - font-weight: normal; - color: #777; - padding-top: 0.5rem; - padding-right: 1rem; - padding-bottom: 0.5rem; - padding-left: 1rem; - font-size: 0.75rem; - font-weight: 400; - color: #6a6e73; -} - -::slotted(hr) { - margin: 0; - border-color: var(--pf-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); - border-style: solid; -} - -::slotted([role="separator"]:not(hr)) { - width: 100%; - height: 1px; - background-color: var(--pf-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); - padding: 0px; - margin: 0px; - border: 0; - display: block; - pointer-events: none; -} diff --git a/elements/pf-dropdown/pf-dropdown-group.ts b/elements/pf-dropdown/pf-dropdown-group.ts deleted file mode 100644 index cabe98d10a..0000000000 --- a/elements/pf-dropdown/pf-dropdown-group.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import styles from './pf-dropdown-group.css'; - -/** - * Represents a group of items for a dropdown component. - * @slot - * Content for the group of dropdown items - */ -@customElement('pf-dropdown-group') -export class PfDropdownGroup extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** - * The label for the group of dropdown items. - */ - @property({ reflect: true }) label?: string; - - render(): TemplateResult<1> { - return html` -

${this.label}

- - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-dropdown-group': PfDropdownGroup; - } -} diff --git a/elements/pf-dropdown/pf-dropdown-item.css b/elements/pf-dropdown/pf-dropdown-item.css deleted file mode 100644 index 5dfb664158..0000000000 --- a/elements/pf-dropdown/pf-dropdown-item.css +++ /dev/null @@ -1,153 +0,0 @@ -:host { - display: block; - width: 100%; - text-align: left; - white-space: nowrap; - border: none; - cursor: pointer; - /** Dropdown item font size */ - font-size: var( - --pf-c-dropdown__menu-item--FontSize, - var(--pf-global--FontSize--md, 1rem) - ); - /** Dropdown item font weight */ - font-weight: var( - --pf-c-dropdown__menu-item--FontWeight, - var(--pf-global--FontWeight--normal, 400) - ); - /** Dropdown item line height */ - line-height: var( - --pf-c-dropdown__menu-item--LineHeight, - var(--pf-global--LineHeight--md, 1.5) - ); - /** Dropdown item color */ - color: var( - --pf-c-dropdown__menu-item--Color, - var(--pf-global--Color--dark-100, #151515) - ); - /** Dropdown item background color */ - background-color: var( - --pf-c-dropdown__menu-item--BackgroundColor, - transparent - ); - border: 3px solid var( - --pf-c-dropdown__menu-item--BackgroundColor, - transparent - ); -} - -:host([hidden]), -[hidden] { - display: none !important; -} - -:host([disabled]), -.disabled { - &, & a { - cursor: not-allowed; - } - --pf-c-dropdown__menu-item--Color: var( - --pf-c-dropdown__menu-item--disabled--Color, - var(--pf-global--Color--dark-200, #6a6e73) - ); - --pf-c-dropdown__menu-item--BackgroundColor: var( - --pf-c-dropdown__menu-item--disabled--BackgroundColor, - transparent - ); -} - -:host(:hover) { - --pf-c-dropdown__menu-item--Color: var( - --pf-c-dropdown__menu-item--hover--Color, - var(--pf-global--Color--dark-100, #151515) - ); - --pf-c-dropdown__menu-item--BackgroundColor: var( - --pf-c-dropdown__menu-item--hover--BackgroundColor, - var(--pf-global--BackgroundColor--light-300, #f0f0f0) - ); - text-decoration: none; -} - -:host(:focus-within) { - border: 3px solid var(--pf-global--link--Color, #0066cc); -} - -#menuitem { - position: relative; - display: flex; - align-items: stretch; - justify-content: space-between; - flex-wrap: wrap; - /** Dropdown item padding */ - padding: var( - /** Dropdown item padding top */ - --pf-c-dropdown__menu-item--PaddingTop, - var(--pf-global--spacer--sm, 0.5rem) - ) - var( - /** Dropdown item padding right */ - --pf-c-dropdown__menu-item--PaddingRight, - var(--pf-global--spacer--md, 1rem) - ) - var( - /** Dropdown item padding bottom */ - --pf-c-dropdown__menu-item--PaddingBottom, - var(--pf-global--spacer--sm, 0.5rem) - ) - var( - /** Dropdown item padding left */ - --pf-c-dropdown__menu-item--PaddingLeft, - var(--pf-global--spacer--md, 1rem) - ); -} - -#item { - display: flex; - align-items: center; -} - -#item:focus { - outline: none; -} - -#item::after { - content: ''; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; -} - -#description { - display: block; - flex: 1 0 100%; - font-size: var( - --pf-c-dropdown__menu-item-description--FontSize, var( - --pf-global--FontSize--sm, 0.75rem - ) - ); - color: var( - --pf-c-dropdown__menu-item-description--Color, var( - --pf-global--Color--dark-200, #6a6e73 - ) - ); - word-break: break-all; -} - -a { - color: var( - --pf-c-dropdown__menu-item--Color, - var(--pf-global--Color--dark-100, #151515) - ) !important; - text-decoration: none !important; -} - -slot:not([name]){ - flex: 1 0 44px; - min-height: 44px; -} - -slot[name="icon"]::slotted(*) { - margin-inline-end: 0.5em; -} diff --git a/elements/pf-dropdown/pf-dropdown-item.ts b/elements/pf-dropdown/pf-dropdown-item.ts deleted file mode 100644 index 29a6dcecee..0000000000 --- a/elements/pf-dropdown/pf-dropdown-item.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; -import { query } from 'lit/decorators/query.js'; -import { consume } from '@lit/context'; - -import { context, type PfDropdownContext } from './context.js'; - -import styles from './pf-dropdown-item.css'; - -export class DropdownItemChange extends Event { - constructor() { - super('change', { bubbles: true, cancelable: true }); - } -} - -/** - * Represents an item for a dropdown component. - * @slot icon - * Optional slot for an icon - * @slot description - * Optional slot for item description - * @slot - - * Content for the dropdown item - */ -@customElement('pf-dropdown-item') -export class PfDropdownItem extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** - * The value associated with the dropdown item. - * This value can be used to identify the selected item - */ - @property({ reflect: true }) value?: string; - - /** - * href for link dropdown items - */ - @property({ attribute: 'href' }) href?: string; - - /** - * Flag indicating whether the item is active - */ - @property({ type: Boolean, reflect: true }) active = false; - - /** - * Indicates whether the dropdown item is disabled. - * A disabled item cannot be selected. - */ - @property({ type: Boolean, reflect: true }) disabled = false; - - /** Item description; overridden by `description` slot */ - @property() description?: string; - - @consume({ context, subscribe: true }) - @property({ attribute: false }) - private ctx?: PfDropdownContext; - - /** @internal */ - @query('#item') menuItem!: HTMLElement; - - protected override updated(changed: PropertyValues): void { - if (changed.has('href')) { - this.dispatchEvent(new DropdownItemChange()); - } - } - - render(): TemplateResult<1> { - const { disabled } = this.ctx ?? { disabled: false }; - const isDisabled = !!this.disabled || !!this.ctx?.disabled; - return html` - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-dropdown-item': PfDropdownItem; - } -} diff --git a/elements/pf-dropdown/pf-dropdown-menu.css b/elements/pf-dropdown/pf-dropdown-menu.css deleted file mode 100644 index 7884b45964..0000000000 --- a/elements/pf-dropdown/pf-dropdown-menu.css +++ /dev/null @@ -1,37 +0,0 @@ -:host { - background: var(--pf-c-dropdown__menu--BackgroundColor, #fff); -} - -.disabled { - --pf-c-dropdown__menu-item--Color: var( - --pf-c-dropdown__menu-item--disabled--Color, - var(--pf-global--Color--dark-200, #6a6e73) - ) !important; - --pf-c-dropdown__menu-item--BackgroundColor: var( - --pf-c-dropdown__menu-item--disabled--BackgroundColor, - transparent - ) !important; -} - -:host([hidden]), -[hidden] { - display: none !important; -} - -::slotted(hr) { - margin: 0; - border-color: var(--pf-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); - border-style: solid; -} - -::slotted([role="separator"]:not(hr)) { - width: 100%; - height: 1px; - background-color: var(--pf-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); - padding: 0px; - margin: 0px; - border: 0; - display: block; - pointer-events: none; -} - diff --git a/elements/pf-dropdown/pf-dropdown-menu.ts b/elements/pf-dropdown/pf-dropdown-menu.ts deleted file mode 100644 index 36a3390040..0000000000 --- a/elements/pf-dropdown/pf-dropdown-menu.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { consume } from '@lit/context'; -import { state } from 'lit/decorators/state.js'; -import { context, type PfDropdownContext } from './context.js'; - -import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import { PfDropdownItem, DropdownItemChange } from './pf-dropdown-item.js'; -import { PfDropdownGroup } from './pf-dropdown-group.js'; - -import styles from './pf-dropdown-menu.css'; -import { classMap } from 'lit/directives/class-map.js'; - -function isDisabledItemClick(event: MouseEvent) { - const item: PfDropdownItem | undefined = - event.composedPath().find((x): x is PfDropdownItem => x instanceof PfDropdownItem); - return !!item?.disabled; -} - -/** - * A **dropdown** presents a menu of actions or links in a constrained space that will trigger a - * process or navigate to a new location. - * @slot - Must contain one or more `` or `` - */ -@customElement('pf-dropdown-menu') -export class PfDropdownMenu extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - @consume({ context, subscribe: true }) - @state() - private ctx?: PfDropdownContext; - - #internals = InternalsController.of(this, { role: 'menu' }); - - get #items() { - return this.items.map(x => x.menuItem); - } - - #tabindex = RovingTabindexController.of(this, { - getItems: () => this.#items, - }); - - /** - * current active descendant in menu - */ - get activeItem(): HTMLElement | null { - return this.#tabindex.items.at(this.#tabindex.atFocusedItemIndex) - ?? this.#tabindex.atFocusableItems.at(0) - ?? null; - } - - /** - * index of current active descendant in menu - */ - get activeIndex(): number { - return this.#tabindex.atFocusedItemIndex; - } - - get items(): PfDropdownItem[] { - return this.#getSlottedItems(this.shadowRoot?.querySelector('slot')); - } - - connectedCallback(): void { - super.connectedCallback(); - this.addEventListener('focusin', this.#onMenuitemFocusin); - this.addEventListener('click', this.#onMenuitemClick); - } - - protected override willUpdate(): void { - this.#internals.ariaDisabled = String(!!this.ctx?.disabled); - } - - render(): TemplateResult<1> { - const { disabled = false } = this.ctx ?? {}; - return html` - - `; - } - - /** - * handles a change event from a dropdown item - * @param event {Event} - */ - #onItemChange(event: Event) { - if (event instanceof DropdownItemChange) { - this.#onSlotChange(); - } - } - - /** - * handles slot change event - */ - #onSlotChange() { - this.#tabindex.items = this.#items; - } - - /** - * handles focusing on an option: - * updates roving tabindex and active descendant - * @param event the focus event - */ - #onMenuitemFocusin(event: FocusEvent) { - if (this.ctx?.disabled) { - event.preventDefault(); - event.stopPropagation(); - } else if (event.target instanceof PfDropdownItem) { - this.#focusItem(event.target.menuItem); - } - } - - /** - * handles clicking on a menuitem: - * which selects an item by default - * or toggles selection if multiselectable - * @param event the click event - */ - #onMenuitemClick(event: MouseEvent) { - if (this.ctx?.disabled || isDisabledItemClick(event)) { - event.preventDefault(); - event.stopPropagation(); - } else if (event.target instanceof PfDropdownItem) { - this.#focusItem(event.target.menuItem); - } - } - - #focusItem(item: HTMLElement) { - const itemIndex = this.#tabindex.items.indexOf(item); - if (itemIndex !== this.#tabindex.atFocusedItemIndex) { - this.#tabindex.atFocusedItemIndex = itemIndex; - } - } - - #getSlottedItems(slot?: HTMLSlotElement | null): PfDropdownItem[] { - return slot - ?.assignedElements() - .flatMap(element => { - if (element instanceof HTMLSlotElement) { - return this.#getSlottedItems(element); - } else if (element instanceof PfDropdownItem) { - return [element]; - } else if (element instanceof PfDropdownGroup) { - return Array.from(element.querySelectorAll('pf-dropdown-item')); - } else { - return []; - } - }) ?? []; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-dropdown-menu': PfDropdownMenu; - } -} diff --git a/elements/pf-dropdown/pf-dropdown.css b/elements/pf-dropdown/pf-dropdown.css deleted file mode 100644 index 7eafb6a620..0000000000 --- a/elements/pf-dropdown/pf-dropdown.css +++ /dev/null @@ -1,351 +0,0 @@ -:host { - display: inline-block; - position: relative; - --pf-c-dropdown__toggle--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); - --pf-c-dropdown__toggle--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); - --pf-c-dropdown__toggle--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--MinWidth: 0; - --pf-c-dropdown__toggle--FontSize: var(--pf-global--FontSize--md, 1rem); - --pf-c-dropdown__toggle--FontWeight: var(--pf-global--FontWeight--normal, 400); - --pf-c-dropdown__toggle--Color: var(--pf-global--Color--100, #151515); - --pf-c-dropdown__toggle--LineHeight: var(--pf-global--LineHeight--md, 1.5); - --pf-c-dropdown__toggle--BackgroundColor: transparent; - --pf-c-dropdown__toggle--before--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-dropdown__toggle--before--BorderTopColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-dropdown__toggle--before--BorderRightColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-dropdown__toggle--before--BorderBottomColor: var(--pf-global--BorderColor--200, #8a8d90); - --pf-c-dropdown__toggle--before--BorderLeftColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-dropdown__toggle--hover--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-dropdown__toggle--focus--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--focus--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-dropdown__toggle--active--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--active--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-dropdown--m-expanded__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown--m-expanded__toggle--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-dropdown__toggle--disabled--BackgroundColor: var(--pf-global--disabled-color--300, #f0f0f0); - --pf-c-dropdown__toggle--m-plain--Color: var(--pf-global--Color--200, #6a6e73); - --pf-c-dropdown__toggle--m-plain--hover--Color: var(--pf-global--Color--100, #151515); - --pf-c-dropdown__toggle--m-plain--disabled--Color: var(--pf-global--disabled-color--200, #d2d2d2); - --pf-c-dropdown__toggle--m-plain--PaddingRight: var(--pf-global--spacer--md, 1rem); - --pf-c-dropdown__toggle--m-plain--PaddingLeft: var(--pf-global--spacer--md, 1rem); - --pf-c-dropdown__toggle--m-plain--child--LineHeight: normal; - --pf-c-dropdown__toggle--m-primary--Color: var(--pf-global--Color--light-100, #fff); - --pf-c-dropdown__toggle--m-primary--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - --pf-c-dropdown__toggle--m-primary--BackgroundColor: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-primary--hover--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-primary--focus--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-primary--active--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown--m-expanded__toggle--m-primary--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-primary--disabled--Color: var(--pf-global--Color--100, #151515); - --pf-c-dropdown__toggle--m-secondary--Color: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-secondary--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - --pf-c-dropdown__toggle--m-secondary--BackgroundColor: transparent; - --pf-c-dropdown__toggle--m-secondary--before--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-dropdown__toggle--m-secondary--hover--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--focus--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--active--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--before--BorderColor: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-secondary--hover--before--BorderColor: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-secondary--focus--before--BorderColor: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-secondary--active--before--BorderColor: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown--m-expanded__toggle--m-secondary--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown--m-expanded__toggle--m-secondary--before--BorderColor: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle-button--Color: var(--pf-global--Color--100, #151515); - --pf-c-dropdown__toggle-progress--Visibility: hidden; - --pf-c-dropdown__toggle-progress--c-spinner--diameter: var(--pf-global--FontSize--sm, 0.875rem); - --pf-c-dropdown__toggle--m-split-button--child--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); - --pf-c-dropdown__toggle--m-split-button--child--PaddingRight: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-dropdown__toggle--m-split-button--child--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); - --pf-c-dropdown__toggle--m-split-button--child--PaddingLeft: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-dropdown__toggle--m-split-button--child--BackgroundColor: transparent; - --pf-c-dropdown__toggle--m-split-button--first-child--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--m-split-button--last-child--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--m-split-button--m-action--child--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--m-split-button--m-action--child--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--m-split-button--m-action__toggle-button--MarginRight: calc(-1 * var(--pf-global--BorderWidth--sm, 1px)); - --pf-c-dropdown__toggle--m-split-button__toggle-check__input--TranslateY: -0.0625rem; - --pf-c-dropdown__toggle--m-split-button__toggle-text--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--m-split-button--child--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - --pf-c-dropdown__toggle--m-split-button--m-primary--child--BorderRadius: var(--pf-c-dropdown__toggle--m-split-button--child--BorderRadius); - --pf-c-dropdown__toggle--m-split-button--m-primary--child--BackgroundColor: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-split-button--m-primary--child--hover--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-split-button--m-primary--child--focus--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-split-button--m-primary--child--active--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-split-button--m-primary--child--m-expanded--BackgroundColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-split-button--m-primary--m-action--child--BorderLeftColor: var(--pf-global--primary-color--200, #004080); - --pf-c-dropdown__toggle--m-split-button--m-primary--m-action--child--BorderLeftWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-dropdown--m-expanded__toggle--m-secondary--m-split-button--child--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--m-split-button--Color: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-secondary--m-split-button--hover--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--m-split-button--focus--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--m-split-button--active--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderWidth--base: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderColor--base: var(--pf-global--primary-color--100, #06c); - --pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderColor: var(--pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderColor--base); - --pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderWidth: var(--pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderWidth--base); - --pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--hover--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--focus--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle--m-secondary--m-split-button--child--before--active--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-dropdown__toggle-icon--LineHeight: var(--pf-global--LineHeight--md, 1.5); - --pf-c-dropdown__toggle-icon--MarginRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle-icon--MarginLeft: var(--pf-global--spacer--md, 1rem); - --pf-c-dropdown--m-top--m-expanded__toggle-icon--Rotate: 180deg; - --pf-c-dropdown--m-plain__toggle-icon--Color: var(--pf-global--Color--200, #6a6e73); - --pf-c-dropdown--m-plain--hover__toggle-icon--Color: var(--pf-global--Color--100, #151515); - --pf-c-dropdown__menu--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #fff); - /** Dropdown box shadow */ - --pf-c-dropdown__menu--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - /** Dropdown top padding */ - --pf-c-dropdown__menu--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__menu--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); - /** Dropdown top position */ - --pf-c-dropdown__menu--Top: calc(100% + var(--pf-global--spacer--xs, 0.25rem)); - /** Dropdown z-index */ - --pf-c-dropdown__menu--ZIndex: var(--pf-global--ZIndex--sm, 200); - --pf-c-dropdown--m-top__menu--Top: 0; - --pf-c-dropdown--m-top__menu--TranslateY: calc(-100% - var(--pf-global--spacer--xs, 0.25rem)); - --pf-c-dropdown__menu-item--BackgroundColor: transparent; - --pf-c-dropdown__menu-item--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** Dropdown menu item right padding */ - --pf-c-dropdown__menu-item--PaddingRight: var(--pf-global--spacer--md, 1rem); - --pf-c-dropdown__menu-item--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__menu-item--PaddingLeft: var(--pf-global--spacer--md, 1rem); - --pf-c-dropdown__menu-item--FontSize: var(--pf-global--FontSize--md, 1rem); - --pf-c-dropdown__menu-item--FontWeight: var(--pf-global--FontWeight--normal, 400); - --pf-c-dropdown__menu-item--LineHeight: var(--pf-global--LineHeight--md, 1.5); - --pf-c-dropdown__menu-item--Color: var(--pf-global--Color--dark-100, #151515); - --pf-c-dropdown__menu-item--hover--Color: var(--pf-global--Color--dark-100, #151515); - --pf-c-dropdown__menu-item--disabled--Color: var(--pf-global--Color--dark-200, #6a6e73); - --pf-c-dropdown__menu-item--hover--BackgroundColor: var(--pf-global--BackgroundColor--light-300, #f0f0f0); - --pf-c-dropdown__menu-item--disabled--BackgroundColor: transparent; - --pf-c-dropdown__menu-item--m-text--Color: var(--pf-global--Color--dark-200, #6a6e73); - --pf-c-dropdown__menu-item-icon--MarginRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__menu-item-icon--Width: var(--pf-global--icon--FontSize--lg, 1.5rem); - --pf-c-dropdown__menu-item-icon--Height: var(--pf-global--icon--FontSize--lg, 1.5rem); - --pf-c-dropdown__menu-item-description--FontSize: var(--pf-global--FontSize--xs, 0.75rem); - --pf-c-dropdown__menu-item-description--Color: var(--pf-global--Color--dark-200, #6a6e73); - --pf-c-dropdown__group--group--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__group-title--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__group-title--PaddingRight: var(--pf-c-dropdown__menu-item--PaddingRight); - --pf-c-dropdown__group-title--PaddingBottom: var(--pf-c-dropdown__menu-item--PaddingBottom); - --pf-c-dropdown__group-title--PaddingLeft: var(--pf-c-dropdown__menu-item--PaddingLeft); - --pf-c-dropdown__group-title--FontSize: var(--pf-global--FontSize--xs, 0.75rem); - --pf-c-dropdown__group-title--FontWeight: var(--pf-global--FontWeight--normal, 400); - --pf-c-dropdown__group-title--Color: var(--pf-global--Color--dark-200, #6a6e73); - --pf-c-dropdown__toggle-image--MarginTop: 0; - --pf-c-dropdown__toggle-image--MarginBottom: 0; - --pf-c-dropdown__toggle-image--MarginRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown--c-divider--MarginTop: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown--c-divider--MarginBottom: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-dropdown__toggle--c-badge__toggle-icon--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-dropdown__toggle--c-badge__toggle-icon--MarginRight: 0; - --pf-c-dropdown--c-menu--Top: calc(100% + var(--pf-global--spacer--xs, 0.25rem)); - --pf-c-dropdown--c-menu--ZIndex: var(--pf-global--ZIndex--sm, 200); - --pf-c-dropdown--m-top--c-menu--Top: 0; - --pf-c-dropdown--m-top--c-menu--TranslateY: calc(-100% - var(--pf-global--spacer--xs, 0.25rem)); - --pf-c-dropdown--m-full-height__toggle--before--BorderTopWidth: 0; - --pf-c-dropdown--m-full-height__toggle--expanded--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); - --pf-c-dropdown--m-full-height__toggle--hover--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); - --pf-c-dropdown--m-full-height__toggle--active--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); - --pf-c-dropdown--m-full-height__toggle--focus--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); - --pf-c-dropdown--m-full-height__toggle--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-dropdown--m-full-height__toggle--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); - --_font-size: var( - --pf-c-dropdown__menu-item--FontSize, - var(--pf-global--FontSize--md, 1rem) - ); - --_font-weight: var( - --pf-c-dropdown__menu-item--FontWeight, - var(--pf-global--FontWeight--normal, 400) - ); - --_line-height: var( - --pf-c-dropdown__menu-item--LineHeight, - var(--pf-global--LineHeight--md, 1.5) - ); - --_color: var( - --pf-c-dropdown__menu-item--Color, - var(--pf-global--Color--dark-100, #151515) - ); - --_background-color: var( - --pf-c-dropdown__menu-item--BackgroundColor, - transparent - ); -} - -:host([disabled]) { - color: var(--pf-global--Color--dark-200, #6a6e73); -} - -:host([hidden]), -[hidden] { - display: none !important; -} - -slot[name="toggle"] { - cursor: pointer; -} - -pf-button#default-toggle, -::slotted([slot="toggle"]) { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - min-width: var(--pf-c-dropdown__toggle--MinWidth); - max-width: 100%; - padding: - var(--pf-c-dropdown__toggle--PaddingTop) - var(--pf-c-dropdown__toggle--PaddingRight) - var(--pf-c-dropdown__toggle--PaddingBottom) - var(--pf-c-dropdown__toggle--PaddingLeft); - font-size: var(--pf-c-dropdown__toggle--FontSize); - font-weight: var(--pf-c-dropdown__toggle--FontWeight); - line-height: var(--pf-c-dropdown__toggle--LineHeight); - color: var(--pf-c-dropdown__toggle--Color); - background-color: var(--pf-c-dropdown__toggle--BackgroundColor); - border: none; -} - -pf-dropdown-menu, -::slotted(pf-dropdown-menu) { - position: absolute !important; - top: var( - --pf-c-dropdown__menu--Top, - calc(100% + var(--pf-global--spacer--xs, 0.25rem)) - ) !important; - left: var( - --pf-c-dropdown__menu--Left, - 0 - ) !important; - right: var( - --pf-c-dropdown__menu--Right, - auto - ) !important; - z-index: var( - --pf-c-dropdown__menu--ZIndex, - var(--pf-global--ZIndex--sm, 200) - ) !important; - padding-top: var( - --pf-c-dropdown__menu--PaddingTop, - var(--pf-global--spacer--sm, 0.5rem) - ) !important; - padding-bottom: var( - --pf-c-dropdown__menu--PaddingBottom, - var(--pf-global--spacer--sm, 0.5rem) - ) !important; - background: var(--pf-c-dropdown__menu--BackgroundColor, #fff) !important; - background-clip: padding-box !important; - box-shadow: var( - --pf-c-dropdown__menu--BoxShadow, - var( - --pf-global--BoxShadow--md, - 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), - 0 0 0.25rem 0 rgba(3, 3, 3, 0.06) - ) - ) !important; - min-width: 100% !important; - margin: 0 !important; -} - -pf-button svg { - width: 1em; - height: 1em; - flex: 0 0 auto; - margin-inline-start: 1em; -} - -::slotted(hr) { - margin: 0; - border-color: var(--pf-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); - border-style: solid; -} - -::slotted([role="separator"]:not(hr)) { - width: 100%; - height: 1px; - background-color: var(--pf-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); - padding: 0px; - margin: 0px; - border: 0; - display: block; -} - -.disabled { - & pf-dropdown-menu, - & ::slotted(pf-dropdown-menu) { - background-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - cursor: not-allowed !important; - } - & pf-button#default-toggle, - & ::slotted([slot="toggle"]) { - --pf-c-button--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--after--BorderColor: var(--pf-c-button--disabled--after--BorderColor); - - --pf-c-button--m-primary--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-primary--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - - --pf-c-button--m-secondary--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-secondary--focus--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-secondary--hover--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-secondary--active--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-secondary--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-secondary--after--BorderColor: transparent; - --pf-c-button--m-secondary--hover--after--BorderColor: transparent; - --pf-c-button--m-secondary--focus--after--BorderColor: transparent; - --pf-c-button--m-secondary--active--after--BorderColor: transparent; - --pf-c-button--m-secondary--focus--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-secondary--hover--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-secondary--active--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-secondary--focus--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - - --pf-c-button--m-control--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-control--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-control--focus--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-control--hover--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-control--active--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-control--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-control--after--BorderColor: transparent; - --pf-c-button--m-control--hover--after--BorderColor: transparent; - --pf-c-button--m-control--focus--after--BorderColor: transparent; - --pf-c-button--m-control--active--after--BorderColor: transparent; - --pf-c-button--m-control--focus--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-control--hover--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-control--active--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-control--focus--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-control--after--BorderBottomColor: transparent; - --pf-c-button--m-control--focus--after--BorderBottomColor: transparent; - --pf-c-button--m-control--hover--after--BorderBottomColor: transparent; - --pf-c-button--m-control--active--after--BorderBottomColor: transparent; - - --pf-c-button--m-plain--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-plain--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-plain--focus--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-plain--hover--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-plain--active--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-plain--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-plain--after--BorderColor: transparent; - --pf-c-button--m-plain--hover--after--BorderColor: transparent; - --pf-c-button--m-plain--focus--after--BorderColor: transparent; - --pf-c-button--m-plain--active--after--BorderColor: transparent; - --pf-c-button--m-plain--focus--Color: var(--pf-c-button--disabled--Color); - --pf-c-button--m-plain--hover--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-plain--active--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - --pf-c-button--m-plain--focus--BackgroundColor: var(--pf-c-button--disabled--BackgroundColor); - } - - & pf-button:focus, - & ::slotted([slot="toggle"]:focus) { - outline: 3px solid var(--pf-global--link--Color, #0066cc); - } -} - -pf-button#default-toggle, -::slotted(pf-button[variant="control"]) { - --_button-icon-vertical-align: -0.25em; - --_button-icon-padding-inline-start: var(--pf-c-dropdown__toggle--PaddingLeft, - var(--pf-global--spacer--md, 1rem)); - --_button-icon-padding-inline-end: var(-pf-c-dropdown__toggle--PaddingRight, - var(--pf-global--spacer--sm, 0.5rem)); -} diff --git a/elements/pf-dropdown/pf-dropdown.ts b/elements/pf-dropdown/pf-dropdown.ts deleted file mode 100644 index 73ee488079..0000000000 --- a/elements/pf-dropdown/pf-dropdown.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; -import { styleMap } from 'lit/directives/style-map.js'; -import { classMap } from 'lit/directives/class-map.js'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { queryAssignedElements } from 'lit/decorators/query-assigned-elements.js'; -import { provide } from '@lit/context'; - -import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; -import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; -import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; - -import { context, type PfDropdownContext } from './context.js'; -import { PfDropdownItem } from './pf-dropdown-item.js'; -import { PfDropdownMenu } from './pf-dropdown-menu.js'; - -import '@patternfly/elements/pf-button/pf-button.js'; - -import styles from './pf-dropdown.css'; - - -export class PfDropdownSelectEvent extends Event { - constructor( - public originalEvent: Event | KeyboardEvent, - public value: string - ) { - super('select', { bubbles: true, cancelable: true }); - } -} - -/** - * A **dropdown** presents a menu of actions or links in a constrained space that - * will trigger a process or navigate to a new location. - * @alias Dropdown - * @fires {PfDropdownSelectEvent} select - when a user select dropdown value - * @fires open - when the dropdown toggles open - * @fires close - when the dropdown toggles closed - */ -@customElement('pf-dropdown') -export class PfDropdown extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** - * When disabled, the dropdown can still be toggled open and closed via keyboard, but menu items cannot be activated. - */ - @property({ type: Boolean, reflect: true }) disabled = false; - - /** - * Whether the dropdown is expanded - */ - @property({ type: Boolean, reflect: true }) expanded = false; - - @queryAssignedElements({ slot: 'toggle', flatten: true }) - private _toggleElements!: HTMLElement[]; - - @queryAssignedElements({ slot: 'menu', flatten: true }) - private _menuElements!: HTMLElement[]; - - @provide({ context }) private ctx: PfDropdownContext = { disabled: false }; - - #logger = new Logger(this); - - #float = new FloatingDOMController(this, { - content: () => this._menuElements?.at(0), - }); - - protected override async getUpdateComplete(): Promise { - const ps = await Promise.all([ - super.getUpdateComplete(), - this._menuElements?.map(x => (x as LitElement).updateComplete), - ]); - return ps.every(x=>!!x); - } - - willUpdate(changed: PropertyValues): void { - if (changed.has('disabled')) { - const { disabled } = this; - this.ctx = { disabled }; - } - } - - render(): TemplateResult<1> { - const { expanded } = this; - const { anchor, alignment, styles = {} } = this.#float; - const { disabled } = this; - return html` -
- - - - Dropdown - - - - - - - - - - - -
`; - } - - override firstUpdated(): void { - this.#onSlotchange(); - } - - updated(changed: PropertyValues): void { - if (changed.has('expanded')) { - this.#expandedChanged(); - } - if (changed.has('disabled')) { - this.#disabledChanged(); - } - } - - #validateDOM() { - const [toggle] = this._toggleElements; - const [menu] = this._menuElements; - if (!toggle) { - this.#logger.warn('no toggle found'); - return false; - } else if (!menu) { - this.#logger.warn('no menu found'); - return false; - } else if (![toggle, menu].map(x => this.shadowRoot?.contains(x)) - .every((p, _, a) => p === a[0])) { - this.#logger.warn('toggle and menu must be located in the same root'); - return false; - } else { - return true; - } - } - - #onSlotchange() { - if (this.#validateDOM()) { - const [menu] = this._menuElements; - const [toggle] = this._toggleElements; - menu.id ||= getRandomId('menu'); - toggle.setAttribute('aria-controls', menu.id); - toggle.setAttribute('aria-haspopup', menu.id); - toggle.setAttribute('aria-expanded', String(this.expanded) as 'true' | 'false'); - } - } - - async #expandedChanged() { - const will = this.expanded ? 'close' : 'open'; - const [menu] = this._menuElements; - const [toggle] = this._toggleElements; - toggle.setAttribute('aria-expanded', `${String(this.expanded) as 'true' | 'false'}`); - this.dispatchEvent(new Event(will)); - if (this.expanded) { - await this.#float.show(); - if (menu instanceof PfDropdownMenu) { - menu.activeItem?.focus(); - } - } else { - await this.#float.hide(); - } - } - - #disabledChanged() { - if (this.#validateDOM()) { - const [toggle] = this._toggleElements; - toggle.setAttribute('aria-disabled', String(!!this.disabled)); - } - } - - #onSelect(event: KeyboardEvent | Event & { target: PfDropdownItem }) { - const [menu] = this._menuElements; - if (menu instanceof PfDropdownMenu) { - const target = event.target as PfDropdownItem || menu.activeItem; - this.dispatchEvent(new PfDropdownSelectEvent(event, `${target?.value}`)); - this.hide(); - } - } - - #onButtonKeydown(event: KeyboardEvent) { - switch (event.key) { - case 'ArrowDown': { - this.show(); - } - } - } - - #onMenuFocusout(event: FocusEvent) { - if (this.expanded) { - const root = this.getRootNode(); - const [menu] = this._menuElements; - if (root instanceof ShadowRoot - || root instanceof Document - && event.relatedTarget instanceof PfDropdownItem - && menu instanceof PfDropdownMenu - && !menu.items.includes(event.relatedTarget) - ) { - this.hide(); - } - } - } - - #onMenuKeydown(event: KeyboardEvent) { - switch (event.key) { - case 'Enter': - case ' ': - event.preventDefault(); - this.#onSelect(event); - break; - case 'Escape': - this.hide(); - this._toggleElements?.at(0)?.focus(); - } - } - - /** - * Opens the dropdown - */ - async show(): Promise { - this.expanded = true; - await this.updateComplete; - } - - /** - * Closes the dropdown - */ - async hide(): Promise { - this.expanded = false; - await this.updateComplete; - } - - async toggle(): Promise { - this.expanded = !this.expanded; - await this.updateComplete; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-dropdown': PfDropdown; - } -} diff --git a/elements/pf-helper-text/demo/dynamic-list.html b/elements/pf-helper-text/demo/dynamic-list.html deleted file mode 100644 index 3b6efbbaf8..0000000000 --- a/elements/pf-helper-text/demo/dynamic-list.html +++ /dev/null @@ -1,30 +0,0 @@ - -
    -
  • - Must be at least 14 characters -
  • -
  • - Cannot contain any variation of the word "redhat" -
  • -
  • - Must include at least 3 of the following: lowercase letter, uppercase letters, numbers, symbols -
  • -
-
- - - - diff --git a/elements/pf-helper-text/demo/dynamic.html b/elements/pf-helper-text/demo/dynamic.html deleted file mode 100644 index e1f2f2a2ee..0000000000 --- a/elements/pf-helper-text/demo/dynamic.html +++ /dev/null @@ -1,42 +0,0 @@ - - This is default helper text - - - - This is indeterminate helper text - - - - This is warning helper text - - - - This is success helper text - - - - This is error helper text - - - - - diff --git a/elements/pf-helper-text/demo/index.html b/elements/pf-helper-text/demo/index.html deleted file mode 100644 index e315ffa07a..0000000000 --- a/elements/pf-helper-text/demo/index.html +++ /dev/null @@ -1 +0,0 @@ -Success! diff --git a/elements/pf-helper-text/demo/multiple.html b/elements/pf-helper-text/demo/multiple.html deleted file mode 100644 index 07e087225c..0000000000 --- a/elements/pf-helper-text/demo/multiple.html +++ /dev/null @@ -1,11 +0,0 @@ - -
    -
  • This is default helper text
  • -
  • This is another default helper text in the same block
  • -
  • And this is more default text in the same block
  • -
-
- - diff --git a/elements/pf-helper-text/demo/static-icons.html b/elements/pf-helper-text/demo/static-icons.html deleted file mode 100644 index aa65975617..0000000000 --- a/elements/pf-helper-text/demo/static-icons.html +++ /dev/null @@ -1,37 +0,0 @@ - - This is default helper text - - - - This is indeterminate helper text - - - - This is warning helper text - - - - This is success helper text - - - - This is error helper text - - - - - diff --git a/elements/pf-helper-text/demo/static.html b/elements/pf-helper-text/demo/static.html deleted file mode 100644 index eb53984246..0000000000 --- a/elements/pf-helper-text/demo/static.html +++ /dev/null @@ -1,16 +0,0 @@ -This is default helper text -This is indeterminate helper text -This is warning helper text -This is success helper text -This is error helper text - - - - diff --git a/elements/pf-helper-text/docs/pf-helper-text.md b/elements/pf-helper-text/docs/pf-helper-text.md deleted file mode 100644 index 3e83d2c20a..0000000000 --- a/elements/pf-helper-text/docs/pf-helper-text.md +++ /dev/null @@ -1,17 +0,0 @@ -{% renderOverview %} - -{% endrenderOverview %} - -{% band header="Usage" %}{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-helper-text/pf-helper-text.css b/elements/pf-helper-text/pf-helper-text.css deleted file mode 100644 index 1c6f815152..0000000000 --- a/elements/pf-helper-text/pf-helper-text.css +++ /dev/null @@ -1,35 +0,0 @@ -:host { - display: flex; - align-items: center; - gap: var(--pf-c-helper-text--Gap, 0.25rem); - font-size: var(--pf-c-helper-text--FontSize, 0.875rem); - color: var(--pf-c-helper-text__item-text--Color, #151515); - line-height: 1.4; -} - -/* Color variants */ -:host([variant='indeterminate']) { - color: var(--pf-c-helper-text__item-text--m-indeterminate--Color, #6a6e73); -} - -:host([variant='warning']) { - color: var(--pf-c-helper-text__item-text--m-warning--Color, #795600); -} - -:host([variant='success']) { - color: var(--pf-c-helper-text__item-text--m-success--Color, #1e4f18); -} - -:host([variant='error']) { - color: var(--pf-c-helper-text__item-text--m-error--Color, #a30000); -} - -::slotted(ul) { - margin: 0; - padding: 0; - list-style-type: none; -} - -pf-icon { - fill: currentColor; -} diff --git a/elements/pf-helper-text/pf-helper-text.ts b/elements/pf-helper-text/pf-helper-text.ts deleted file mode 100644 index 506f716eb2..0000000000 --- a/elements/pf-helper-text/pf-helper-text.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import '@patternfly/elements/pf-icon/pf-icon.js'; - -import styles from './pf-helper-text.css'; - -/** Map of status to default icons (Font Awesome solid set). */ -const StatusIconMap = { - success: 'check-circle', - warning: 'exclamation-triangle', - error: 'exclamation-circle', - indeterminate: 'info-circle', -}; - -/** - * Displays contextual feedback for form fields with optional icon and status color. - * - * @slot icon - Optional custom icon to override the default icon. - * @slot - Default slot for the helper text content. - * - * @fires icon-load - Fired when the icon successfully loads. - * @fires icon-error - Fired if loading the icon fails. - * - * @csspart icon - The container for the icon. - * @csspart text - The container for the text. - */ -@customElement('pf-helper-text') -export class PfHelperText extends LitElement { - public static readonly styles: CSSStyleSheet[] = [styles]; - - /** - * Defines the helper text status and its corresponding color and icon. - */ - @property({ reflect: true }) variant: - | 'default' - | 'success' - | 'warning' - | 'error' - | 'indeterminate' = 'default'; - - /** - * Custom icon name to override the default icon. - * Requires `` to be imported. - */ - @property() icon?: string; - - /** - * Icon set for custom icons (e.g., 'fas', 'patternfly'). - */ - @property({ attribute: 'icon-set' }) iconSet?: string; - - #slots = new SlotController(this, 'icon', null); - - /** - * Determine the effective icon to display. - */ - private get _resolvedIcon(): string | undefined { - if (this.icon) { - return this.icon; - } - if (this.variant !== 'default') { - return StatusIconMap[this.variant]; - } - return undefined; - } - - protected render(): TemplateResult<1> { - const iconName = this._resolvedIcon; - const showIcon = !!this.icon || this.#slots.hasSlotted('icon'); - - return html` - - - - - - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-helper-text': PfHelperText; - } -} diff --git a/elements/pf-helper-text/test/pf-helper-text.spec.ts b/elements/pf-helper-text/test/pf-helper-text.spec.ts deleted file mode 100644 index 3f40a1058d..0000000000 --- a/elements/pf-helper-text/test/pf-helper-text.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfHelperText } from '@patternfly/elements/pf-helper-text/pf-helper-text.js'; - -describe('', function() { - describe('simply instantiating', function() { - let element: PfHelperText; - it('imperatively instantiates', function() { - expect(document.createElement('pf-helper-text')).to.be.an.instanceof(PfHelperText); - }); - - it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-helper-text'); - expect(element) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfHelperText); - }); - }); - - describe('variant property', function() { - it('should default to "default" variant', async function() { - const element = await createFixture(html``); - expect(element.variant).to.equal('default'); - }); - - it('should reflect variant attribute', async function() { - const element = await createFixture(html``); - expect(element.variant).to.equal('success'); - expect(element.getAttribute('variant')).to.equal('success'); - }); - }); - - describe('icon display', function() { - it('should show icon when icon property is set', async function() { - const element = await createFixture(html`Success`); - await element.updateComplete; - const iconContainer = element.shadowRoot?.querySelector('#icon'); - expect(iconContainer?.hasAttribute('hidden')).to.be.false; - }); - - it('should hide icon when no icon or slotted icon is present', async function() { - const element = await createFixture(html`Text`); - await element.updateComplete; - const iconContainer = element.shadowRoot?.querySelector('#icon'); - expect(iconContainer?.hasAttribute('hidden')).to.be.true; - }); - }); - - describe('text content', function() { - it('should render text content in default slot', async function() { - const element = await createFixture(html`Helper text content`); - expect(element.textContent?.trim()).to.equal('Helper text content'); - }); - - it('should have aria-live on text container', async function() { - const element = await createFixture(html`Text`); - await element.updateComplete; - const textContainer = element.shadowRoot?.querySelector('#text'); - expect(textContainer?.getAttribute('aria-live')).to.equal('polite'); - }); - }); -}); diff --git a/elements/pf-hint/demo/index.html b/elements/pf-hint/demo/index.html deleted file mode 100644 index 43fb03bd61..0000000000 --- a/elements/pf-hint/demo/index.html +++ /dev/null @@ -1,47 +0,0 @@ -

Default with no title

- - Welcome to the new documentation experience. - Learn more about the improved features. - - - - - - - Link - Action - Disabled Link - Disabled Action -
- Separated Link - Separated Action -
-
- Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. - Try it for 90 days -
- - - - diff --git a/elements/pf-hint/demo/with-title.html b/elements/pf-hint/demo/with-title.html deleted file mode 100644 index a511307ad5..0000000000 --- a/elements/pf-hint/demo/with-title.html +++ /dev/null @@ -1,59 +0,0 @@ -

Hint with title

- - - - - - Link - Action - Disabled Link - Disabled Action -
- Separated Link - Separated Action -
-
- Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. -
- - - - - - Link - Action - Disabled Link - Disabled Action -
- Separated Link - Separated Action -
-
- Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. - Try it for 90 days -
- - - - diff --git a/elements/pf-hint/docs/pf-hint.md b/elements/pf-hint/docs/pf-hint.md deleted file mode 100644 index d9f8030343..0000000000 --- a/elements/pf-hint/docs/pf-hint.md +++ /dev/null @@ -1,104 +0,0 @@ -{% renderOverview %} - - Welcome to the new documentation experience. - Learn more about the improved features. - -{% endrenderOverview %} - -{% band header="Usage" %} - ### Default with no title - Basic hint without a title, used for simple informational messages. - - {% htmlexample %} - - Welcome to the new documentation experience. - Learn more about the improved features. - - {% endhtmlexample %} - - ### Hint with title - Add a title to your hint by using the `title` slot to provide more context. - - {% htmlexample %} - - Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. - - {% endhtmlexample %} - - ### With a footer - Add action links or buttons to the footer using the `footer` slot. - - {% htmlexample %} - - Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. - - Try it for 90 days - - - {% endhtmlexample %} - - ### With actions - Add an actions menu (like a kebab dropdown) using the `actions` slot. - - {% htmlexample %} - - - - - - Link - - - - - - - Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. - - {% endhtmlexample %} - - ### Complete example - A hint with all available slots. - - {% htmlexample %} - - - - - - Link - - - - - - Disabled link - - - - Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. - - Try it for 90 days - - - {% endhtmlexample %} - -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-hint/pf-hint.css b/elements/pf-hint/pf-hint.css deleted file mode 100644 index 19d60d76b6..0000000000 --- a/elements/pf-hint/pf-hint.css +++ /dev/null @@ -1,120 +0,0 @@ -:host { - display: block; - - /* Container */ - --pf-c-hint--GridRowGap: var(--pf-global--spacer--md, 1rem); - --pf-c-hint--PaddingTop: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-hint--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-hint--PaddingBottom: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-hint--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-hint--BackgroundColor: var(--pf-global--palette--blue-50, #e7f1fa); - --pf-c-hint--BorderColor: var(--pf-global--palette--blue-100, #bee1f4); - --pf-c-hint--BorderWidth: 1px; - --pf-c-hint--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - --pf-c-hint--BoxShadow: var(--pf-global--BoxShadow--sm, 0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06)); - - /* Title */ - --pf-c-hint__title--FontSize: 1.125rem; - --pf-c-hint__title--FontWeight: var(--pf-global--FontWeight--semi-bold, 700); - --pf-c-hint__title--Color: var(--pf-global--Color--100, #151515); - --pf-c-hint__title--LineHeight: var(--pf-global--LineHeight--md, 1.5); - - /* Body */ - --pf-c-hint__body--FontSize: 1rem; - --pf-c-hint__body--Color: var(--pf-global--Color--100, #151515); - --pf-c-hint__body--LineHeight: var(--pf-global--LineHeight--md, 1.5); - - /* Footer */ - --pf-c-hint__footer--MarginTop: 0; - --pf-c-hint__footer--child--MarginRight: 1rem; - - /* Actions */ - --pf-c-hint__actions--Top: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-hint__actions--Right: var(--pf-global--spacer--lg, 1.5rem); - --pf-c-hint__actions--MarginLeft: 3rem; - --pf-c-hint__actions--c-dropdown--MarginTop: calc(0.375rem * -1); -} - -#container { - position: relative; - display: grid; - grid-template-columns: 1fr auto; - gap: var(--pf-c-hint--GridRowGap); - padding: - var(--pf-c-hint--PaddingTop) - var(--pf-c-hint--PaddingRight) - var(--pf-c-hint--PaddingBottom) - var(--pf-c-hint--PaddingLeft); - background-color: var(--pf-c-hint--BackgroundColor); - border: var(--pf-c-hint--BorderWidth) solid var(--pf-c-hint--BorderColor); - border-radius: var(--pf-c-hint--BorderRadius); - box-shadow: var(--pf-c-hint--BoxShadow); - overflow: visible; -} - -#container > * { - grid-column: 1; -} - -#actions { - grid-column: 2; - grid-row: 1 / -1; - align-self: start; - position: relative; - z-index: 1000; -} - -#actions ::slotted(pf-dropdown) { - margin-top: var(--pf-c-hint__actions--c-dropdown--MarginTop); - --pf-c-dropdown__menu--MinWidth: auto; - --pf-c-dropdown__menu--Left: auto; - --pf-c-dropdown__menu--Right: 0; -} - -#title { - font-size: var(--pf-c-hint__title--FontSize); - font-weight: var(--pf-c-hint__title--FontWeight); - color: var(--pf-c-hint__title--Color); - line-height: var(--pf-c-hint__title--LineHeight); -} - -#body { - font-size: var(--pf-c-hint__body--FontSize); - color: var(--pf-c-hint__body--Color); - line-height: var(--pf-c-hint__body--LineHeight); -} - -#footer { - margin-top: var(--pf-c-hint__footer--MarginTop); - font-size: var(--pf-c-hint__body--FontSize); - line-height: var(--pf-c-hint__body--LineHeight); -} - -#footer ::slotted(*) { - margin-right: var(--pf-c-hint__footer--child--MarginRight); -} - -#footer ::slotted(*:last-child) { - margin-right: 0; -} - -/* Hidden elements */ -[hidden] { - display: none !important; -} - -/* Link styling within hint */ -::slotted(a) { - color: var(--pf-global--link--Color, #06c); - text-decoration: none; -} - -::slotted(a:hover) { - color: var(--pf-global--link--Color--hover, #004080); - text-decoration: underline; -} - -/* Button styling in footer */ -::slotted(pf-button) { - --pf-c-button--m-link--Color: var(--pf-global--link--Color, #06c); -} diff --git a/elements/pf-hint/pf-hint.ts b/elements/pf-hint/pf-hint.ts deleted file mode 100644 index d3fe94a80a..0000000000 --- a/elements/pf-hint/pf-hint.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import styles from './pf-hint.css'; - -/** - * A **hint** is in-app messaging that provides a one-step reminder, explanation, - * or call to action for a page or modal. Hints provide information about an interaction - * or prerequisite step that might not be immediately obvious to the user. - * - * @summary Provides inline contextual help or information to users - * @alias Hint - */ -@customElement('pf-hint') -export class PfHint extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - #slots = new SlotController(this, 'title', null, 'footer', 'actions'); - - render(): TemplateResult<1> { - const hasActions = !this.#slots.isEmpty('actions'); - - return html` -
-
- - -
-
- - -
-
- - -
- -
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-hint': PfHint; - } -} diff --git a/elements/pf-hint/test/pf-hint.spec.ts b/elements/pf-hint/test/pf-hint.spec.ts deleted file mode 100644 index b7b7fc2302..0000000000 --- a/elements/pf-hint/test/pf-hint.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfHint } from '@patternfly/elements/pf-hint/pf-hint.js'; -import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; - -describe('', function() { - describe('simply instantiating', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-hint')).to.be.an.instanceof(PfHint); - }); - - it('should upgrade', async function() { - const el = await createFixture(html` - Basic hint - `); - const klass = customElements.get('pf-hint'); - expect(el) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfHint); - }); - }); - - describe('basic hint', function() { - let element: PfHint; - beforeEach(async function() { - element = await createFixture(html` - Welcome to the new documentation experience. - `); - }); - - it('should render body content, and not title footer, or actions', async function() { - const snap = await a11ySnapshot(); - expect(snap.children?.length).to.equal(1); - }); - }); - - describe('hint with title', function() { - let element: PfHint; - beforeEach(async function() { - element = await createFixture(html` - - Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management. - - `); - await element.updateComplete; - }); - - it('should render title and body content', async function() { - const snap = await a11ySnapshot(); - expect(snap.children?.length).to.equal(2); - }); - }); - - describe('hint with footer', function() { - let element: PfHint; - beforeEach(async function() { - element = await createFixture(html` - - Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management. - Try it for 90 days - - `); - await element.updateComplete; - }); - - it('should render footer', function() { - const footer = element.shadowRoot!.querySelector('#footer'); - expect(footer).to.exist; - }); - }); - - describe('hint with actions', function() { - let element: PfHint; - beforeEach(async function() { - element = await createFixture(html` - - - Do more with Find it Fix it capabilities - Upgrade to Red Hat Smart Management. - - `); - await element.updateComplete; - }); - - it('should render title, body, and actions', async function() { - const { children: [actions, title, body, ...rest] = [] } = await a11ySnapshot(); - expect(actions.role).to.equal('button'); - expect(title.role).to.equal('text'); - expect(body.role).to.equal('text'); - expect(rest.length).to.equal(0); - }); - }); -}); diff --git a/elements/pf-icon/README.md b/elements/pf-icon/README.md deleted file mode 100644 index f739929014..0000000000 --- a/elements/pf-icon/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# PatternFly Elements Icon - -Read more about Icon in the [PatternFly Elements Icon documentation][docs] - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im/@patternfly/elements/), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-icon/pf-icon.js'; -``` - -## Usage - -Place the icon element on the page and give it an icon name from the [default icon set][icon-sets]. -In most cases, the icon should be labelled using `aria-label` or `aria-labelledby`, or removed from -the accessibility tree with `aria-hidden="true"` or `role="presentation"`, if its content is merely -presentational and expressed using accessible text copy elsewhere. - -```html - -``` - -### Fallback Content - -If you wish to display some content while the icon loads (or while JS is disabled), -you can slot it into ``. For instance, when using a checkmark icon in a -server status table, you may wish to display a checkmark emoji if JS is disabled. - -```html - -``` - -### Icon Sets - -Icon comes with three built-in icon sets: - -1. `fas`: Font Awesome Free Solid icons (the default set) -1. `far`: Font Awesome Free Regular icons -1. `fab`: Font Awesome Free Bold icons -1. `patternfly`: PatternFly icons - -Use the `set` attribute to pick an alternative icon set. -```html - - - -``` - -It is possible to add custom icon sets or override the default sets. -Icon sets are defined in detail in [the docs][icon-sets]. - -### Bundling and custom loading behaviour - -When bundling `` with other modules (e.g. using webpack, rollup, -esbuild, vite, or similar tools), icon imports will be code-split into chunks, -as they are imported from the `@patternfly/icons` package. Ensure that your -bundler is configured to permit dynamic imports, or mark the `@patternfly/icons` -package as "external" and apply an [import map][importmap] to your page instead. -If you would like to -customize the loading behaviour, override the `PfIcon.resolve()` static method. -This methods takes two arguments: the icon set (a string) and the icon name -(a string), and returns a promise of the icon contents, which is a DOM node, or -[anything else that lit-html can render][renderable]. - -```js -import { PfIcon } from '/pfe.min.js'; -PfIcon.resolve = async function(set, icon) { - try { - const { default: content } = await import(`/assets/icons/${set}/${icon}.js`); - if (content instanceof Node) { - return content.cloneNode(true); - } - } catch (e) { - return ''; - } -} -``` - -## Loading - -Icons load _lazily_ by default, meaning that the browser will not attempt to fetch and render the -icon until it scrolls into view. You can change this with the `loading` attribute; -see the [docs][docs] for more info. - -[docs]: https://patternflyelements.org/components/icon/ -[icon-sets]: https://patternflyelements.org/components/icon/#icon-sets -[renderable]: https://lit.dev/docs/components/rendering/#renderable-values -[importmap]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap diff --git a/elements/pf-icon/demo/custom-icon-sets.html b/elements/pf-icon/demo/custom-icon-sets.html deleted file mode 100644 index 9e62817bd6..0000000000 --- a/elements/pf-icon/demo/custom-icon-sets.html +++ /dev/null @@ -1,38 +0,0 @@ -
-

Custom Icon Sets

-

Call PfIcon.addIconSet with a string (icon set name) and a function taking the - icon set name and the icon name, and returning a URL (object) to a javascript module whose - default export is a lit renderable.

- - - - - - - -
- - - - diff --git a/elements/pf-icon/demo/index.html b/elements/pf-icon/demo/index.html deleted file mode 100644 index 611437d509..0000000000 --- a/elements/pf-icon/demo/index.html +++ /dev/null @@ -1,55 +0,0 @@ -
-

Icon Sizes and Colours

-
-
Small
-
Medium
-
Large
-
X-Large
-
-
-
danger
-
default
-
info
-
success
-
warning
-
disabled
-
-
- - - - - diff --git a/elements/pf-icon/docs/pf-icon.md b/elements/pf-icon/docs/pf-icon.md deleted file mode 100644 index c6578df53b..0000000000 --- a/elements/pf-icon/docs/pf-icon.md +++ /dev/null @@ -1,194 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - - - -{% renderOverview %} - - - - -{% endrenderOverview %} - -{% band header="Usage" %} - Place the icon element on the page and give it an icon name from the [default - icon set][icon-sets]. In most cases, the icon should be labelled using - `aria-label` or `aria-labelledby`, or removed from the accessibility tree - with `aria-hidden="true"` or `role="presentation"`, if its content is merely - presentational and expressed using accessible text copy elsewhere. - - {% htmlexample %} - - - - - {% endhtmlexample %} - - ### Size - The default size is 1em, so icon size matches text size. `2x`, etc, are - multiples of font size. `sm`, `md`, etc are fixed pixel-based sizes. - - {% htmlexample %} - - - - - {% endhtmlexample %} -{% endband %} - -{% band header="Icon sets" %} -Icon comes with three built-in icon sets: - -1. `fas`: Font Awesome Free Solid icons (the default set) -1. `far`: Font Awesome Free Regular icons -1. `patternfly`: PatternFly icons - -Use the `set` attribute to pick an alternative icon set. -```html - - - -``` - -### Register a New Icon Set - -Icons are JavaScript module which export a [lit renderable][renderable], -typically an inline SVG element [template literal][template-literals] tagged -with the Lit [`svg`][svg-tag] template tag. To register a new icon set, call -the static `addIconSet` method with the set name and a getter function. The -getter function takes the icon set and icon name and returns a promise containing -the icon node, or any other [renderable][renderable] value. - -```ts -type IconResolveFunction = (set: string, icon: string) => Promise | Node; -``` - -```javascript -import { PfIcon } from '@patternfly/pf-icon'; - -// Or, in a non-module context: -// const PfIcon = await customElements.whenDefined('pf-icon'); - -PfIcon.addIconSet('local', (set, icon) => - import(`/assets/icons/${set}-${icon}.js`)) - .then(mod => mod.default); -``` - -### Updating an Existing Icon Set - -To updating an existing icon set, you use the same `addIconSet` function. By -defaulting back to then existing `getIconUrl` method, you you can add a new -icon to an existing set: - -```js -PfIcon.addIconSet('patternfly', (set, icon) => { - switch (icon) { - // add your custom icons - case 'my-custom-icon': - case 'other-custom-icon': - return import(`/icon-overrides/patternfly-custom/${icon}.js`) - .then(mod => mod.default); - // fall back to built-in icons - default: - return PfIcon.getIconUrl(set, icon); - } -}); -``` - -### Override the Default Icon Sets - -Icons are [loaded lazily](#loading) by default, so there's no performance -penalty for keeping the default icon sets around and unused. However, if you'd -like to override the default icon sets across the entire page, you can use -`addIconSet` with the `fas`, `far`, and `patternfly` set names: - -```js -import { PfIcon } from '@patternfly/pf-icon'; - -PfIcon.resolve = (set, icon) => - import(`/icons/${set}-${icon}.js`)) - .then(mod => mod.default); -``` - -Now when `` is loaded from the [RedHat DX -CDN](https://redhatstatic.com/dx/), it will automatically load icon modules from -the CDN as well. - -{% endband %} - -{% band header="Loading" %} - -Icons load _lazily_ by default, meaning that the browser will not attempt to -fetch and render the icon until it scrolls into view. You can change this with -the `loading` attribute, which has three values: - -1. `lazy` (the default): load icons when they scroll into view -2. `idle`: load each icon on the page as soon as the browser enters an [idle - state][ric] Or, on less-capable browsers, at the next frame -3. `eager`: each icon will begin to load and render the moment it connects to - the DOM. - -You might choose to enable eager rendering for "above-the-fold" content, but -keep lazy loading for the rest of the page. - -```html -
-

- - Hot News! -

-
- -
-
...
-
- -
-

- - Contact Us -

- ... -
-``` -{% endband %} - -{% renderSlots %} - -If you wish to display some content while the icon loads (or while JS is -disabled), you can slot it into ``. For instance, when using a -checkmark icon in a server status table, you may wish to display a checkmark -emoji if JS is disabled. - -```html - -``` - -It's recommended to use the icon name in the default slot, or -`aria-label(ledby)` so that the icon is accessible to screen readers. - -```html -check -``` -{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} - -[renderable]: https://lit.dev/docs/components/rendering/ -[template-literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals -[svg-tag]: https://lit.dev/docs/api/templates/#svg -[ric]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback diff --git a/elements/pf-icon/pf-icon.css b/elements/pf-icon/pf-icon.css deleted file mode 100644 index 5abec7c804..0000000000 --- a/elements/pf-icon/pf-icon.css +++ /dev/null @@ -1,35 +0,0 @@ -:host { - position: relative; - display: inline-block; - line-height: 0; - height: fit-content !important; - width: fit-content !important; -} - -#container { - display: grid; - grid-template: 1fr / 1fr; - place-content: center; -} - -#container.content ::slotted(*) { - display: none; -} - -svg { - fill: currentcolor; -} - -:host([size=sm]) #container { --_size: var(--pf-global--icon--FontSize--sm, 10px); } -:host([size=md]) #container { --_size: var(--pf-global--icon--FontSize--md, 18px); } -:host([size=lg]) #container { --_size: var(--pf-global--icon--FontSize--lg, 24px); } -:host([size=xl]) #container { --_size: var(--pf-global--icon--FontSize--xl, 54px); } - -#container, svg { - /** size of the icon */ - width: var(--pf-icon--size, var(--_size)); - height: var(--pf-icon--size, var(--_size)); - min-width: var(--pf-icon--size, var(--_size)); - min-height: var(--pf-icon--size, var(--_size)); -} - diff --git a/elements/pf-icon/pf-icon.ts b/elements/pf-icon/pf-icon.ts deleted file mode 100644 index 4bc8885513..0000000000 --- a/elements/pf-icon/pf-icon.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { state } from 'lit/decorators/state.js'; - -import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; - -import style from './pf-icon.css'; - -type Renderable = unknown; - -export type IconResolverFunction = (set: string, icon: string) => - Renderable | Promise; - -/** - * requestIdleCallback when available, requestAnimationFrame when not - * @param f callback - */ -const ric: typeof globalThis.requestIdleCallback = - globalThis.requestIdleCallback - ?? globalThis.requestAnimationFrame - ?? (async (f: () => void) => Promise.resolve().then(f)); - -/** Fired when an icon fails to load */ -export class IconResolveError extends ErrorEvent { - constructor( - set: string, - icon: string, - /** The original error when importing the icon module */ - public originalError: Error - ) { - super('error', { message: `Could not load icon "${icon}" from set "${set}".` }); - } -} - -/** - * An **icon** component is a container that allows for icons of varying dimensions to - * seamlessly replace each other without shifting surrounding content. - * @alias Icon - * @fires load - Fired when an icon is loaded and rendered - * @fires error - Fired when an icon fails to load - */ -@customElement('pf-icon') -export class PfIcon extends LitElement { - public static readonly styles: CSSStyleSheet[] = [style]; - - private static onIntersect: IntersectionObserverCallback = records => - records.forEach(({ isIntersecting, target }) => { - const icon = target as PfIcon; - icon.#intersecting = isIntersecting; - ric(() => { - if (icon.#intersecting) { - icon.#load(); - } - }); - }); - - private static defaultResolve: IconResolverFunction = (set: string, icon: string): Renderable => - import(`@patternfly/icons/${set}/${icon}.js`) - .then(mod => mod.default.cloneNode(true)); - - private static io = new IntersectionObserver(PfIcon.onIntersect); - - private static resolvers = new Map(); - - private static instances = new Set(); - - /** - * Register a new icon set - * @param setName - The name of the icon set - * @param resolver - A function that returns the URL of an icon - * @example returning a URL object - * ```js - * PfIcon.addIconSet('rh', (set, icon) => - * new URL(`./icons/${set}/${icon}.js`, import.meta.url)); - * ``` - * @example returning a string - * ```js - * PfIcon.addIconSet('rh', (set, icon) => - * `/assets/icons/${set}/${icon}.js`); - * ``` - */ - public static addIconSet(setName: string, resolver: IconResolverFunction): void { - if (typeof setName !== 'string') { - Logger.warn(`[${this.name}]: the first argument to addIconSet must be a string.`); - } else if (typeof resolver !== 'function') { - Logger.warn(`[${this.name}]: the second argument to addIconSet must be a function.`); - } else { - this.resolvers.set(setName, resolver); - for (const instance of this.instances) { - instance.#load(); - } - } - } - - /** Removes all added icon sets and resets resolve function */ - public static reset(): void { - this.resolvers.clear(); - this.resolve = this.defaultResolve; - } - - /** - * Gets a renderable icon. Override this to customize how icons are resolved. - * @param set - The name of the icon set - * @param icon - The name of the icon - * @returns The icon content, a node or anything else which lit-html can render - * @example resolving an icon node from an icon module - * ```js - * PfIcon.resolve = (set, icon) => - * import(`/assets/icons/${set}/${icon}.js`) - * .then(mod => mod.default.cloneNode(true)); - * ``` - * @example resolving a named export from an icon collection module - * ```js - * PfIcon.resolve = (set, icon) => - * import(`/assets/icons.js`) - * .then(module => module[icon]?.cloneNode(true)); - * ``` - * @example resolving a new node from an svg file - * ```js - * const iconCacne = new Map(); - * function getCachedIconOrNewNode(set, icon, svg) { - * const key = `${set}_${icon}`; - * if (!iconCache.has(key)) { - * const template = document.createElement('template'); - * template.innerHTML = svg; - * iconCache.set(key, template); - * } - * return iconCache.get(key); - * } - * PfIcon.resolve = (set, icon) => - * fetch(`/assets/icons/${set}/${icon}.svg`) - * .then(response => response.text()) - * .then(svg => getCachedIconOrNewNode(set, icon, svg)) - * .then(node => node.content.cloneNode(true)); - * ``` - */ - public static resolve: IconResolverFunction = PfIcon.defaultResolve; - - /** Icon set */ - @property() set = 'fas'; - - /** Icon name */ - @property({ reflect: true }) icon = ''; - - /** Size of the icon */ - @property({ reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'sm'; - - /** - * Controls how eager the element will be to load the icon data - * - `eager`: eagerly load the icon, blocking the main thread - * - `idle`: wait for the browser to attain an idle state before loading - * - `lazy` (default): wait for the element to enter the viewport before loading - */ - @property() loading?: 'idle' | 'lazy' | 'eager' = 'lazy'; - - /** Icon content. Any value that lit can render */ - @state() private content?: unknown; - - #intersecting = false; - - #logger = new Logger(this); - - #lazyLoad() { - PfIcon.io.observe(this); - if (this.#intersecting) { - this.load(); - } - } - - #load() { - switch (this.loading) { - case 'idle': return void ric(() => this.load()); - case 'lazy': return void this.#lazyLoad(); - case 'eager': return void this.load(); - } - } - - async #contentChanged() { - await this.updateComplete; - this.dispatchEvent?.(new Event('load', { bubbles: true })); - } - - connectedCallback(): void { - super.connectedCallback(); - PfIcon.instances.add(this); - } - - willUpdate(changed: PropertyValues): void { - if (changed.has('icon')) { - this.#load(); - } - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - PfIcon.io.unobserve(this); - PfIcon.instances.delete(this); - } - - render(): TemplateResult<1> { - const content = this.content ?? ''; - return html` - - `; - } - - protected async load(): Promise { - const { set, icon } = this; - const resolver = PfIcon.resolvers.get(set) ?? PfIcon.resolve; - if (set && icon && typeof resolver === 'function') { - try { - this.content = await resolver(set, icon); - this.#contentChanged(); - } catch (error: unknown) { - this.#logger.error((error as IconResolveError).message); - this.dispatchEvent?.(new IconResolveError(set, icon, error as Error)); - } - } - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-icon': PfIcon; - } -} diff --git a/elements/pf-jump-links/README.md b/elements/pf-jump-links/README.md deleted file mode 100644 index a9305b1685..0000000000 --- a/elements/pf-jump-links/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Jump Links - -**Jump links** allow users to navigate to sections within a page. - -Read more about Jump Links in the [documentation][docs]. - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-jump-links/pf-jump-links.js'; -``` - -[docs]: https://patternflyelements.org/components/jump-links diff --git a/elements/pf-jump-links/demo/centered-list.html b/elements/pf-jump-links/demo/centered-list.html deleted file mode 100644 index a135970593..0000000000 --- a/elements/pf-jump-links/demo/centered-list.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - Inactive section - Active section - Inactive section - -
- - - - diff --git a/elements/pf-jump-links/demo/expandable-vertical-with-subsection.html b/elements/pf-jump-links/demo/expandable-vertical-with-subsection.html deleted file mode 100644 index 7ac64c359b..0000000000 --- a/elements/pf-jump-links/demo/expandable-vertical-with-subsection.html +++ /dev/null @@ -1,27 +0,0 @@ -
- - Inactive section - - Section with active subsection - - Active subsection - Inactive subsection - Inactive subsection - - - Inactive section - Inactive section - -
- - - - diff --git a/elements/pf-jump-links/demo/index.html b/elements/pf-jump-links/demo/index.html deleted file mode 100644 index d4bb930006..0000000000 --- a/elements/pf-jump-links/demo/index.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - Inactive section - Active section - Inactive section - -
- - - - diff --git a/elements/pf-jump-links/demo/label.html b/elements/pf-jump-links/demo/label.html deleted file mode 100644 index 943eeb4423..0000000000 --- a/elements/pf-jump-links/demo/label.html +++ /dev/null @@ -1,24 +0,0 @@ -
- - Inactive section - Active section - Inactive section - - - Inactive section - Active section - Inactive section - -
- - - - diff --git a/elements/pf-jump-links/demo/vertical-with-label.html b/elements/pf-jump-links/demo/vertical-with-label.html deleted file mode 100644 index 02572f05a6..0000000000 --- a/elements/pf-jump-links/demo/vertical-with-label.html +++ /dev/null @@ -1,19 +0,0 @@ -
-

Vertical with label

- - Inactive section - Active section - Inactive section - -
- - - - diff --git a/elements/pf-jump-links/demo/vertical.html b/elements/pf-jump-links/demo/vertical.html deleted file mode 100644 index dcd6217667..0000000000 --- a/elements/pf-jump-links/demo/vertical.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - Inactive section - Active section - Inactive section - -
- - - - diff --git a/elements/pf-jump-links/docs/pf-jump-links.md b/elements/pf-jump-links/docs/pf-jump-links.md deleted file mode 100644 index 6fe941bcdc..0000000000 --- a/elements/pf-jump-links/docs/pf-jump-links.md +++ /dev/null @@ -1,99 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} -
-

Basic

- - Inactive section - Active section - Inactive section - -
-{% endrenderOverview %} - -{% band header="Usage" %} -
-

With centered list

- {% htmlexample %} - - Inactive section - Active section - Inactive section - - {% endhtmlexample %} -
- -
-

With label

- {% htmlexample %} - - Inactive section - Active section - Inactive section - - {% endhtmlexample %} -
- -
-

Vertical

- {% htmlexample %} - - Inactive section - Active section - Inactive section - - {% endhtmlexample %} -
- -
-

Expandable vertical with subsection

- {% htmlexample %} - - Inactive section - - Section with active subsection - - Active subsection - Inactive subsection - Inactive subsection - - - Inactive section - Inactive section - - {% endhtmlexample %} -
- -
-

Expandable vertical with Scrollspy

- {% htmlexample %} - - Heading 1 - Heading 2 - Heading 3 - Heading 4 - Heading 5 - - {% endhtmlexample %} -
-{%endband %} - -{% renderSlots %}{% endrenderSlots %} -{% renderSlots for="pf-jump-links-list", level=3, header="Slots on `pf-jump-links-list`" %}{% endrenderSlots %} -{% renderSlots for="pf-jump-links-item", level=3, header="Slots on `pf-jump-links-item`" %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-jump-links/pf-jump-links-item.css b/elements/pf-jump-links/pf-jump-links-item.css deleted file mode 100644 index a852a8e822..0000000000 --- a/elements/pf-jump-links/pf-jump-links-item.css +++ /dev/null @@ -1,75 +0,0 @@ -:host { - display: block; -} - -#container { - display: contents; -} - -slot:not([name]) { - display: flex; - flex-direction: column; - row-gap: var(--pf-global--spacer--md, 1rem); -} - -a { - position: relative; - display: flex; - cursor: pointer; - flex: 1; - /** Top padding around the link */ - padding-block-start: var(--pf-c-jump-links__link--PaddingTop, - var(--pf-global--spacer--md, 1rem)); - /** Right padding around the link */ - padding-inline-end: var(--pf-c-jump-links__link--PaddingRight, - var(--pf-global--spacer--md, 1rem)); - /** Bottom padding around the link */ - padding-block-end: var(--pf-c-jump-links__link--PaddingBottom, - var(--pf-global--spacer--md, 1rem)); - /** Left padding around the link */ - padding-inline-start: var(--pf-c-jump-links__link--PaddingLeft, - var(--pf-global--spacer--md, 1rem)); - text-decoration: none; - /** Outline offset for jump links link */ - outline-offset: var(--pf-c-jump-links__link--OutlineOffset, - calc(-1 * var(--pf-global--spacer--sm, 0.5rem))); - /** Color for jump links link text */ - color: var(--pf-c-jump-links__link-text--Color, - var(--pf-global--Color--200, #6a6e73)); -} - -a::before { - position: absolute; - inset: 0; - pointer-events: none; - content: ""; - border-color: var(--pf-c-jump-links__link--before--BorderColor, transparent); - border-style: solid; - border-width: - var(--pf-c-jump-links__link--before--BorderTopWidth, - var(--pf-c-jump-links__list--before--BorderTopWidth, - var(--pf-global--BorderWidth--sm, 1px))) - var(--pf-c-jump-links__link--before--BorderRightWidth, 0) - var(--pf-c-jump-links__link--before--BorderBottomWidth, 0) - var(--pf-c-jump-links__link--before--BorderLeftWidth, 0); -} - -a:hover { - --pf-c-jump-links__link-text--Color: var(--pf-c-jump-links__link--hover__link-text--Color, - var(--pf-global--Color--100, #151515)); -} - -a:focus { - --pf-c-jump-links__link-text--Color: var(--pf-c-jump-links__link--focus__link-text--Color, - var(--pf-global--Color--100, #151515)); -} - -:host([active]) { - --pf-c-jump-links__link--before--BorderTopWidth: var(--pf-c-jump-links__item--m-current__link--before--BorderTopWidth, - var(--pf-global--BorderWidth--lg, 3px)); - --pf-c-jump-links__link--before--BorderLeftWidth: var(--pf-c-jump-links__item--m-current__link--before--BorderLeftWidth, 0); - --pf-c-jump-links__link--before--BorderColor: var(--pf-c-jump-links__item--m-current__link--before--BorderColor, - var(--pf-global--primary-color--100, #06c)); - --pf-c-jump-links__link-text--Color: var(--pf-c-jump-links__item--m-current__link-text--Color, - var(--pf-global--Color--100, #151515)); -} diff --git a/elements/pf-jump-links/pf-jump-links-item.ts b/elements/pf-jump-links/pf-jump-links-item.ts deleted file mode 100644 index 474367757a..0000000000 --- a/elements/pf-jump-links/pf-jump-links-item.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { html, LitElement, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import { ifDefined } from 'lit/directives/if-defined.js'; - -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import style from './pf-jump-links-item.css'; - -import { observes } from '@patternfly/pfe-core/decorators/observes.js'; - -@customElement('pf-jump-links-item') -export class PfJumpLinksItem extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** Whether this item is active. */ - @property({ type: Boolean, reflect: true }) active = false; - - /** hypertext reference for this link */ - @property({ reflect: true }) href?: string; - - #internals = InternalsController.of(this, { role: 'listitem' }); - - render(): TemplateResult<1> { - return html` - - - - - `; - } - - @observes('active') - protected activeChanged(): void { - this.#internals.ariaCurrent = this.active ? 'location' : null; - } - - #onClick() { - this.dispatchEvent(new Event('select', { bubbles: true })); - } - - #onFocus() { - this.dispatchEvent(new Event('focus', { bubbles: true })); - } -} - - -declare global { - interface HTMLElementTagNameMap { - 'pf-jump-links-item': PfJumpLinksItem; - } -} diff --git a/elements/pf-jump-links/pf-jump-links-list.css b/elements/pf-jump-links/pf-jump-links-list.css deleted file mode 100644 index 7fb888fcdf..0000000000 --- a/elements/pf-jump-links/pf-jump-links-list.css +++ /dev/null @@ -1,18 +0,0 @@ -:host { - display: block; -} - -#container { - display: contents; - --pf-c-jump-links__list--PaddingTop: 0; - --pf-c-jump-links__list--PaddingBottom: 0; - /** Top padding around each link */ - --pf-c-jump-links__link--PaddingTop: var(--pf-c-jump-links__list__list__link--PaddingTop, - var(--pf-global--spacer--sm, 0.5rem)); - /** Bottom padding around each link */ - --pf-c-jump-links__link--PaddingBottom: var(--pf-c-jump-links__list__list__link--PaddingBottom, - var(--pf-global--spacer--sm, 0.5rem)); - /** Left padding around each link */ - --pf-c-jump-links__link--PaddingLeft: var(--pf-c-jump-links__list__list__link--PaddingLeft, - var(--pf-global--spacer--lg, 1.5rem)); -} diff --git a/elements/pf-jump-links/pf-jump-links-list.ts b/elements/pf-jump-links/pf-jump-links-list.ts deleted file mode 100644 index 3253f493c1..0000000000 --- a/elements/pf-jump-links/pf-jump-links-list.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { html, LitElement, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; - -import style from './pf-jump-links-list.css'; - -@customElement('pf-jump-links-list') -export class PfJumpLinksList extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - render(): TemplateResult<1> { - // TODO: add label - // eslint-disable-next-line lit-a11y/accessible-name - return html`
`; - } -} - - -declare global { - interface HTMLElementTagNameMap { - 'pf-jump-links-list': PfJumpLinksList; - } -} diff --git a/elements/pf-jump-links/pf-jump-links.css b/elements/pf-jump-links/pf-jump-links.css deleted file mode 100644 index 9a25810914..0000000000 --- a/elements/pf-jump-links/pf-jump-links.css +++ /dev/null @@ -1,175 +0,0 @@ -[hidden] { - display: none !important; -} - -:host { - display: block; -} - -#container { - display: flex; - flex-wrap: wrap; - font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); - font-size: var(--pf-global--FontSize--md, 16px); - font-weight: var(--pf-global--FontWeight--normal, 400); -} - -slot { - position: relative; - /** Controls the display type of the list container */ - display: var(--pf-c-jump-links__list--Display, flex); - /** Controls the direction of items in the list */ - flex-direction: var(--pf-c-jump-links__list--FlexDirection, row); - /** Padding around the list of links */ - padding-block-start: var(--pf-c-jump-links__list--PaddingTop, 0); - /** Right padding for the list */ - padding-inline-end: var(--pf-c-jump-links__list--PaddingRight, - var(--pf-global--spacer--md, 1rem)); - /** Bottom padding for the list */ - padding-block-end: var(--pf-c-jump-links__list--PaddingBottom, 0); - /** Left padding for the list */ - padding-inline-start: var(--pf-c-jump-links__list--PaddingLeft, - var(--pf-global--spacer--md, 1rem)); - /** Controls visibility of the list */ - visibility: var(--pf-c-jump-links__list--Visibility, visible); -} - -slot::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - content: ""; - /** Border color for the list pseudo-element */ - border: solid var(--pf-c-jump-links__list--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2)); - border-width: - /** Top border width for the list pseudo-element */ - var(--pf-c-jump-links__list--before--BorderTopWidth, - var(--pf-global--BorderWidth--sm, 1px)) - /** Right border width for the list pseudo-element */ - var(--pf-c-jump-links__list--before--BorderRightWidth, 0) - /** Bottom border width for the list pseudo-element */ - var(--pf-c-jump-links__list--before--BorderBottomWidth, 0) - /** Left border width for the list pseudo-element */ - var(--pf-c-jump-links__list--before--BorderLeftWidth, 0); -} - -:host([vertical]) #container { - /** Top padding for vertical layout */ - --pf-c-jump-links__list--PaddingTop: - var(--pf-c-jump-links--m-vertical__list--PaddingTop, - var(--pf-global--spacer--md, 1rem) - ); - /** Right padding for vertical layout */ - --pf-c-jump-links__list--PaddingRight: - var(--pf-c-jump-links--m-vertical__list--PaddingRight,0); - /** Bottom padding for vertical layout */ - --pf-c-jump-links__list--PaddingBottom: - var(--pf-c-jump-links--m-vertical__list--PaddingBottom, - var(--pf-global--spacer--md, 1rem) - ); - /** Left padding for vertical layout */ - --pf-c-jump-links__list--PaddingLeft: - var(--pf-c-jump-links--m-vertical__list--PaddingLeft, 0); - /** Top border width for vertical layout */ - --pf-c-jump-links__list--before--BorderTopWidth: - var(--pf-c-jump-links--m-vertical__list--before--BorderTopWidth, 0); - /** Left border width for vertical layout */ - --pf-c-jump-links__list--before--BorderLeftWidth: - var(--pf-c-jump-links--m-vertical__list--before--BorderLeftWidth, - var(--pf-global--BorderWidth--sm, 1px)); - /** Top border width for current item in vertical layout */ - --pf-c-jump-links__item--m-current__link--before--BorderTopWidth: - var(--pf-c-jump-links--m-vertical__item--m-current__link--before--BorderTopWidth, 0); - /** Left border width for current item in vertical layout */ - --pf-c-jump-links__item--m-current__link--before--BorderLeftWidth: - var(--pf-c-jump-links--m-vertical__item--m-current__link--before--BorderLeftWidth, - var(--pf-global--BorderWidth--lg, 3px) - ); - /** Flex direction for vertical layout */ - --pf-c-jump-links__list--FlexDirection: var(--pf-c-jump-links--m-vertical__list--FlexDirection, column); -} - -:host([centered]) #container { - justify-content: center; -} - -:host([centered]) #label { - text-align: center; -} - -:host(:not([expandable])) #label { - display: block; - width: 100%; - margin-block-end: var(--pf-c-jump-links__label--MarginBottom, - var(--pf-global--spacer--md, 1rem)); -} - -:host([expandable]) { - /** Base margin for the toggle button */ - --pf-c-jump-links--m-expanded__toggle--MarginBottom: - calc(var(--pf-c-jump-links__toggle--MarginBottom--base, - calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)) - ) + var(--pf-global--spacer--md, 1rem)); -} - -summary { - display: block; - /** Padding around the expandable jump links disclosure widget. */ - margin-block-start: var(--pf-c-jump-links__toggle--MarginTop, - calc(-1 * var(--pf-global--spacer--form-element, 0.375rem))); - /** Bottom margin for the toggle */ - margin-block-end: var(--pf-c-jump-links__toggle--MarginBottom, - var(--pf-c-jump-links__toggle--MarginBottom--base, - calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)))); - /** Left margin for the toggle */ - margin-inline-start: var(--pf-c-jump-links__toggle--MarginLeft); - /** Color of the toggle text */ - color: var(--pf-c-jump-links__toggle-text--Color, - var(--pf-global--Color--100, #151515)); - /** Padding around the expandable jump links disclosure widget. */ - padding: - var(--pf-c-button--PaddingTop, - var(--pf-global--spacer--form-element, 0.375rem)) - /** Right padding for the button */ - var(--pf-c-button--PaddingRight, - var(--pf-global--spacer--md, 1rem)) - /** Bottom padding for the button */ - var(--pf-c-button--PaddingBottom, - var(--pf-global--spacer--form-element, 0.375rem)) - /** Left padding for the button */ - var(--pf-c-button--PaddingLeft, - var(--pf-global--spacer--md, 1rem)); -} - -summary pf-icon { - /** Rotation angle for the toggle icon */ - rotate: var(--pf-c-jump-links__toggle-icon--Rotate, 0); - /** Transition for the toggle icon */ - transition: var(--pf-c-jump-links__toggle-icon--Transition, - var(--pf-global--Transition, all 250ms cubic-bezier(0.42, 0, 0.58, 1))); -} - -summary span { - /** Left margin for the toggle text */ - margin-inline-start: var(--pf-c-jump-links__toggle-text--MarginLeft, - var(--pf-global--spacer--md, 1rem)); -} - -:host([expanded]) #container { - /** Bottom margin for expanded toggle */ - --pf-c-jump-links__toggle--MarginBottom: var(--pf-c-jump-links--m-expanded__toggle--MarginBottom, - calc(var(--pf-c-jump-links__toggle--MarginBottom--base, - calc(-1 * var(--pf-global--spacer--form-element, 0.375rem))) + - var(--pf-global--spacer--md, 1rem))); - /** Rotation for expanded toggle icon */ - --pf-c-jump-links__toggle-icon--Rotate: var(--pf-c-jump-links--m-expanded__toggle-icon--Rotate, - 90deg); - /** Color for expanded toggle icon */ - --pf-c-jump-links__toggle-icon--Color: var(--pf-c-jump-links--m-expanded__toggle-icon--Color, - var(--pf-global--Color--100, #151515)); - --pf-c-jump-links__toggle-icon--Rotate: 90deg; -} diff --git a/elements/pf-jump-links/pf-jump-links.ts b/elements/pf-jump-links/pf-jump-links.ts deleted file mode 100644 index 4d2f9970c1..0000000000 --- a/elements/pf-jump-links/pf-jump-links.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { html, LitElement, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import { ScrollSpyController } from '@patternfly/pfe-core/controllers/scroll-spy-controller.js'; -import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; - -import { PfJumpLinksItem } from './pf-jump-links-item.js'; - -import '@patternfly/elements/pf-icon/pf-icon.js'; - -import style from './pf-jump-links.css'; - -/** - * **Jump links** allow users to navigate to sections within a page. - * @alias Jump Links - * @fires toggle - when the `expanded` disclosure widget is toggled - */ -@customElement('pf-jump-links') -export class PfJumpLinks extends LitElement { - static readonly styles: CSSStyleSheet[] = [style]; - - /** Whether the element features a disclosure widget around the nav items */ - @property({ reflect: true, type: Boolean }) expandable = false; - - /** Whether the expandable element's disclosure widget is expanded */ - @property({ reflect: true, type: Boolean }) expanded = false; - - /** Whether the layout of children is vertical or horizontal. */ - @property({ reflect: true, type: Boolean }) vertical = false; - - /** Whether to center children. */ - @property({ reflect: true, type: Boolean }) centered = false; - - /** Offset to add to the scroll position, potentially for a masthead which content scrolls under. */ - @property({ type: Number }) offset = 0; - - /** Label to add to nav element. Required for accessibility. */ - @property({ reflect: true }) label = 'Jump to section'; - - #kids = this.querySelectorAll?.(':is(pf-jump-links-item, pf-jump-links-list)'); - - get #items() { - return Array.from(this.#kids ?? []) - .flatMap(i => [ - ...i.shadowRoot?.querySelectorAll?.('a') ?? [], - ...i.querySelectorAll?.('a') ?? [], - ]); - } - - #tabindex = RovingTabindexController.of(this, { - getItems: () => this.#items, - }); - - #spy = new ScrollSpyController(this, { - rootMargin: `${this.offset}px 0px 0px 0px`, - tagNames: ['pf-jump-links-item'], - }); - - protected override async getUpdateComplete(): Promise { - const here = await super.getUpdateComplete(); - const ps = await Promise.all(Array.from(this.#kids, x => x.updateComplete)); - return here && ps.every(x => !!x); - } - - override connectedCallback(): void { - super.connectedCallback(); - this.addEventListener('slotchange', this.#onSlotChange); - this.addEventListener('select', this.#onSelect); - } - - override firstUpdated(): void { - const active = this.querySelector?.('pf-jump-links-item[active]'); - if (active) { - this.#setActiveItem(active); - } - } - - override updated(changed: Map): void { - if (changed.has('offset')) { - this.#spy.rootMargin = `${this.offset ?? 0}px 0px 0px 0px`; - } - } - - render(): TemplateResult<1> { - return html` - - `; - } - - #onSlotChange() { - this.#tabindex.items = this.#items; - } - - #onSelect(event: Event) { - if (event.target instanceof PfJumpLinksItem) { - this.#setActiveItem(event.target); - } - } - - #setActiveItem(item: PfJumpLinksItem) { - const itemLink = item.shadowRoot?.querySelector?.('a') ?? null; - if (itemLink) { - this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(itemLink); - this.#spy.setActive(item); - } - } - - #onToggle(event: Event) { - if (event.target instanceof HTMLDetailsElement) { - this.expanded = event.target.open; - } - this.dispatchEvent(new Event('toggle')); - } -} - - -declare global { - interface HTMLElementTagNameMap { - 'pf-jump-links': PfJumpLinks; - } -} diff --git a/elements/pf-jump-links/test/pf-jump-links.spec.ts b/elements/pf-jump-links/test/pf-jump-links.spec.ts deleted file mode 100644 index dea84bd3fd..0000000000 --- a/elements/pf-jump-links/test/pf-jump-links.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { expect, html, nextFrame } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { sendKeys } from '@web/test-runner-commands'; - -import { PfJumpLinks } from '../pf-jump-links.js'; -import { PfJumpLinksItem } from '../pf-jump-links-item.js'; -import { PfJumpLinksList } from '../pf-jump-links-list.js'; - -import '@patternfly/pfe-tools/test/stub-logger.js'; -import { allUpdates } from '@patternfly/pfe-tools/test/utils.js'; - -describe('', function() { - let element: PfJumpLinks; - let firstItem: PfJumpLinksItem; - let secondItem: PfJumpLinksItem; - - beforeEach(async function() { - element = await createFixture(html` - - Inactive section - Active section - Inactive section - - `); - await allUpdates(element); - [firstItem, secondItem] = element.querySelectorAll('pf-jump-links-item'); - }); - - it('imperatively instantiates', function() { - expect(document.createElement('pf-jump-links')).to.be.an.instanceof(PfJumpLinks); - expect(document.createElement('pf-jump-links-item')).to.be.an.instanceof(PfJumpLinksItem); - expect(document.createElement('pf-jump-links-list')).to.be.an.instanceof(PfJumpLinksList); - }); - - describe('tabbing to first item', function() { - let initialActiveElement: Element | null; - beforeEach(async function() { - initialActiveElement = document.activeElement; - await sendKeys({ press: 'Tab' }); - await nextFrame(); - }); - - it('should focus the first jump-links-item', function() { - expect(document.activeElement).to.equal(firstItem); - }); - - describe('pressing right arrow key', function() { - beforeEach(async function() { - await sendKeys({ press: 'ArrowRight' }); - await allUpdates(element); - await nextFrame(); - }); - it('should focus a jump-links-item', function() { - expect(document.activeElement).to.be.an.instanceof(PfJumpLinksItem); - }); - it('should change focus when keyboard navigation is used', function() { - expect(document.activeElement).to.not.equal(initialActiveElement); - }); - it('should focus the second jump links item', function() { - expect(document.activeElement).to.equal(secondItem); - }); - }); - }); -}); - -describe('', function() { - let element: PfJumpLinksItem; - - beforeEach(async function() { - element = await createFixture(html` - - `); - }); - - it('should upgrade', async function() { - expect(element) - .to.be.an.instanceof(customElements.get('pf-jump-links-item')) - .and - .to.be.an.instanceof(PfJumpLinksItem); - }); -}); - -describe('', function() { - let element: PfJumpLinksList; - - beforeEach(async function() { - element = await createFixture(html` - - `); - }); - - it('should upgrade', async function() { - expect(element) - .to.be.an.instanceof(customElements.get('pf-jump-links-list')) - .and - .to.be.an.instanceof(PfJumpLinksList); - }); -}); diff --git a/elements/pf-label-group/README.md b/elements/pf-label-group/README.md deleted file mode 100644 index f966d3d3c8..0000000000 --- a/elements/pf-label-group/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Label Group -A label group is a collection of labels that can be grouped by category and -used to represent one or more values assigned to a single attribute. When -the number of labels exceeds the configured limit, additional labels are -hidden under an overflow indicator. - -Read more about Label Group in the [PatternFly Elements Label Group documentation](https://patternflyelements.org/components/label-group) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-label-group/pf-label-group.js'; -``` - -## Usage - -```html - - Security - Performance - Networking - -``` - -With a category: -```html - - Filters - Security - Performance - -``` diff --git a/elements/pf-label-group/demo/index.html b/elements/pf-label-group/demo/index.html deleted file mode 100644 index fe2b946d5c..0000000000 --- a/elements/pf-label-group/demo/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - Blue - Green - Orange - - - diff --git a/elements/pf-label-group/demo/label-group-closeable.html b/elements/pf-label-group/demo/label-group-closeable.html deleted file mode 100644 index bc8be03463..0000000000 --- a/elements/pf-label-group/demo/label-group-closeable.html +++ /dev/null @@ -1,10 +0,0 @@ - - Filters - Security - Performance - Networking - - - diff --git a/elements/pf-label-group/demo/label-group-vertical.html b/elements/pf-label-group/demo/label-group-vertical.html deleted file mode 100644 index ce0ae80f05..0000000000 --- a/elements/pf-label-group/demo/label-group-vertical.html +++ /dev/null @@ -1,10 +0,0 @@ - - Group - Label 1 - Label 2 - Label 3 - - - diff --git a/elements/pf-label-group/demo/label-group-with-a-very-long-name.html b/elements/pf-label-group/demo/label-group-with-a-very-long-name.html deleted file mode 100644 index cce515ce8a..0000000000 --- a/elements/pf-label-group/demo/label-group-with-a-very-long-name.html +++ /dev/null @@ -1,10 +0,0 @@ - - Label group with a very long category name - Label 1 - Label 2 - Label 3 - - - diff --git a/elements/pf-label-group/demo/label-group-with-overflow-labels.html b/elements/pf-label-group/demo/label-group-with-overflow-labels.html deleted file mode 100644 index c7f438ef9e..0000000000 --- a/elements/pf-label-group/demo/label-group-with-overflow-labels.html +++ /dev/null @@ -1,13 +0,0 @@ - - Tags - Label 1 - Label 2 - Label 3 - Label 4 - Label 5 - Label 6 - - - diff --git a/elements/pf-label-group/docs/pf-label-group.md b/elements/pf-label-group/docs/pf-label-group.md deleted file mode 100644 index 58771e058a..0000000000 --- a/elements/pf-label-group/docs/pf-label-group.md +++ /dev/null @@ -1,17 +0,0 @@ -{% renderOverview %} - -{% endrenderOverview %} - -{% band header="Usage" %}{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-label-group/pf-label-group.css b/elements/pf-label-group/pf-label-group.css deleted file mode 100644 index a10d5fd020..0000000000 --- a/elements/pf-label-group/pf-label-group.css +++ /dev/null @@ -1,100 +0,0 @@ -:host { - --pf-c-label-group__list--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - --pf-c-label-group__list--MarginRight: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - --pf-c-label-group--m-category--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-label-group--m-category--PaddingRight: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-label-group--m-category--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-label-group--m-category--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-label-group--m-category--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); - --pf-c-label-group--m-category--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); - --pf-c-label-group__label--MarginRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-label-group__label--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - --pf-c-label-group__label--MaxWidth: 18ch; - --pf-c-label-group__close--MarginTop: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - --pf-c-label-group__close--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); - --pf-c-label-group__list-item--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-label-group__list-item--MarginBottom: var(--pf-global--spacer--xs, 0.25rem); - display: inline-flex; - flex-wrap: wrap; - align-items: center; - min-width: 0; - max-width: 100%; - color: var(--pf-global--Color--100, #151515); -} - -[hidden], -.empty, -::slotted([hidden]), -::slotted([overflow-hidden]) { - display: none !important; -} - -#outer { - display: inline-flex; - flex: 1; - flex-wrap: wrap; - align-items: center; - min-width: 0; - column-gap: var(--pf-global--spacer--xs, 0.25rem); - border-radius: var(--pf-global--BorderRadius--sm, 3px); - padding: 0 !important; -} - -#outer.has-category { - padding: - var(--pf-c-label-group--m-category--PaddingTop) - var(--pf-c-label-group--m-category--PaddingRight) - var(--pf-c-label-group--m-category--PaddingBottom) - var(--pf-c-label-group--m-category--PaddingLeft) !important; - background-color: var(--pf-c-label-group--m-category--BackgroundColor); -} - -#labels { - margin-inline-end: var(--pf-c-label-group__list--MarginRight); - margin-block-end: var(--pf-c-label-group__list--MarginBottom); - font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); - font-size: var(--pf-global--FontSize--sm, 14px); - font-weight: var(--pf-global--FontWeight--normal, 400); - line-height: 1.6; -} - -#labels ::slotted(pf-label) { - display: inline-flex; - min-width: 0; - margin-inline-end: var(--pf-c-label-group__list-item--MarginRight); - margin-block-end: var(--pf-c-label-group__list-item--MarginBottom); -} - -::slotted([slot="category"]) { - display: inline-block; - max-inline-size: var(--pf-c-label-group__label--MaxWidth); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - vertical-align: middle; - margin-inline-end: var(--pf-c-label-group__label--MarginRight); - font-size: var(--pf-c-label-group__label--FontSize); -} - -:host([orientation="vertical"]) #outer { - flex-direction: column; - align-items: flex-start; -} - -#close-button { - --pf-icon--size: 16px; - margin-block: var(--pf-c-label-group__close--MarginTop) var(--pf-c-label-group__close--MarginBottom); - inset-block-start: 0.125em; -} - -.visually-hidden { - border: 0; - clip: rect(0, 0, 0, 0); - block-size: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - inline-size: 1px; -} diff --git a/elements/pf-label-group/pf-label-group.ts b/elements/pf-label-group/pf-label-group.ts deleted file mode 100644 index 9a6446c48e..0000000000 --- a/elements/pf-label-group/pf-label-group.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { LitElement, html, isServer, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { observes } from '@patternfly/pfe-core/decorators/observes.js'; -import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; - -import { PfLabel } from '../pf-label/pf-label.js'; - -import styles from './pf-label-group.css'; - -export class PfLabelGroupExpandEvent extends Event { - constructor() { - super('expand', { bubbles: true, cancelable: true }); - } -} - -export class PfLabelGroupRemoveEvent extends Event { - constructor() { - super('remove', { bubbles: true, cancelable: true }); - } -} - -/** - * `${` - * **WS** (_>= 0x_) - * `remaining` - * **WS** (_>= 0x_) - * `}` - */ -const REMAINING_RE = /\$\{\s*remaining\s*\}/g; - -/** - * A **label group** is a collection of labels that can be grouped by category - * and used to represent one or more values assigned to a single attribute. - * When the number of labels exceeds `numLabels`, additional labels will be - * hidden using an overflow label. - * - * @summary Groups multiple labels with overflow, category, and close support. - * - * @fires {PfLabelGroupExpandEvent} expand - Fires when label group is expanded to show all labels - * @fires {PfLabelGroupRemoveEvent} remove - Fires when label group is closed/removed - * - * @slot category - * Category name text for label group category. - * If this slot is populated, the label group will have category styling applied. - * @slot - `` elements. - */ -@customElement('pf-label-group') -export class PfLabelGroup extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** Orientation of the label group. */ - @property({ reflect: true }) orientation: 'horizontal' | 'vertical' = 'horizontal'; - - /** Accessible label for the label group when no category name is provided. */ - @property({ attribute: 'accessible-label' }) accessibleLabel = ''; - - /** Accessible label for the close button. */ - @property({ attribute: 'accessible-close-label' }) accessibleCloseLabel = 'Close'; - - /** - * Customizable "more" template string. - * Use variable `${remaining}` for the overflow label count. - */ - @property({ attribute: 'collapsed-text' }) collapsedText = '${remaining} more'; - - /** Customizable "show less" text string. */ - @property({ attribute: 'expanded-text' }) expandedText = 'show less'; - - /** Number of labels to show before overflow. */ - @property({ attribute: 'num-labels', type: Number }) numLabels = 3; - - /** Whether overflow labels are visible. */ - @property({ reflect: true, type: Boolean }) open = false; - - /** Whether the label group can be closed. */ - @property({ reflect: true, type: Boolean }) closeable = false; - - /** Label count tracked during SSR via child events. */ - _ssrLabelCount = 0; - - get #overflowLabel(): PfLabel | null { - return this.renderRoot?.querySelector?.('#overflow') ?? null; - } - - get #closeButton(): HTMLButtonElement | null { - return this.renderRoot?.querySelector?.('#close-button') ?? null; - } - - get #categorySlotted(): Node[] { - const slot = this.renderRoot - ?.querySelector?.('slot[name="category"]'); - return slot?.assignedNodes({ flatten: true }) ?? []; - } - - get #labels(): NodeListOf | PfLabel[] { - if (isServer) { - return [] as PfLabel[]; - } - return this.querySelectorAll('pf-label:not([slot]):not([overflow-label])'); - } - - get #labelCount(): number { - if (isServer) { - return this._ssrLabelCount; - } - return this.#labels.length; - } - - get #hasCategory(): boolean { - return this.#categorySlotted.length > 0; - } - - get #remaining(): number { - return this.#labelCount - this.numLabels; - } - - #tabindex = RovingTabindexController.of(this, { - getItems: () => [ - ...Array.prototype.slice.call( - this.#labels, - 0, - this.open ? this.#labels.length : Math.min(this.#labels.length, this.numLabels), - ), - this.#overflowLabel, - this.#closeButton, - ].filter(x => !!x), - }); - - constructor() { - super(); - this.addEventListener('remove', this.#onRemove); - this.addEventListener('ssr:label', () => this._ssrLabelCount++); - } - - override render(): TemplateResult<1> { - const empty = this.#labelCount <= 0; - return html` - - `; - } - - /** Updates labels when relevant properties change. */ - @observes('numLabels') - @observes('closeable') - @observes('open') - private labelsChanged(): void { - this.#updateOverflow(); - } - - #onCloseClick() { - this.dispatchEvent(new PfLabelGroupRemoveEvent()); - } - - async #onMoreClick(event: Event) { - event.stopPropagation(); - this.open = !this.open; - await this.updateComplete; - this.labelsChanged(); - if (this.#overflowLabel) { - this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(this.#overflowLabel); - } - this.dispatchEvent(new PfLabelGroupExpandEvent()); - } - - #onSlotchange() { - this.requestUpdate(); - } - - #onRemove(event: Event) { - if (event instanceof PfLabelGroupRemoveEvent) { - this.remove(); - } - } - - #updateOverflow() { - this.#labels.forEach((label, i) => { - label.hidden = i >= this.numLabels && !this.open; - }); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-label-group': PfLabelGroup; - } -} diff --git a/elements/pf-label-group/test/pf-label-group.spec.ts b/elements/pf-label-group/test/pf-label-group.spec.ts deleted file mode 100644 index 5a79c3a6d1..0000000000 --- a/elements/pf-label-group/test/pf-label-group.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { a11ySnapshot, querySnapshotAll } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; -import { PfLabelGroup } from '../pf-label-group.js'; -import { sendKeys } from '@web/test-runner-commands'; - -function press(key: string) { - return async function() { - await sendKeys({ press: key }); - }; -} - -describe('', function() { - let element: PfLabelGroup; - - describe('simply instantiating', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-label-group')).to.be.an.instanceof(PfLabelGroup); - }); - - it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-label-group'); - expect(element) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfLabelGroup); - }); - }); - - describe('with 4 labels', function() { - beforeEach(async function() { - element = await createFixture(html` - - Label 1 - Label 2 - Label 3 - Label 4 - - `); - }); - - it('displays 3 labels and an overflow button', async function() { - const snapshot = await a11ySnapshot(); - expect(querySnapshotAll(snapshot, { name: /^Label/ })).to.have.length(3); - }); - - describe('clicking overflow label', function() { - beforeEach(async function() { - const overflow = element.shadowRoot?.querySelector('#overflow') as HTMLElement; - overflow?.click(); - await element.updateComplete; - }); - it('should show all labels', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(4); - }); - it('should show collapse text', async function() { - const snapshot = await a11ySnapshot(); - expect(querySnapshotAll(snapshot, { name: 'show less' })).to.have.length(1); - }); - }); - }); - - describe('with 4 labels and `closeable` attribute', function() { - const updateComplete = () => element.updateComplete; - - beforeEach(async function() { - element = await createFixture(html` - - Label 1 - Label 2 - Label 3 - Label 4 - - `); - }); - - beforeEach(updateComplete); - - it('should have a close button', async function() { - const snapshot = await a11ySnapshot(); - const last = snapshot.children?.at(-1); - expect(last?.name).to.equal('Close'); - expect(last?.role).to.equal('button'); - }); - - describe('clicking close button', function() { - beforeEach(function() { - element.focus(); - }); - beforeEach(press('ArrowLeft')); - beforeEach(press('Enter')); - beforeEach(updateComplete); - it('should remove element', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children).to.not.be.ok; - }); - }); - }); - - describe('with 4 labels and custom text attributes', function() { - beforeEach(async function() { - element = await createFixture(html` - - Label 1 - Label 2 - Label 3 - Label 4 - - `); - }); - - it('is accessible', async function() { - await expect(element).to.be.accessible(); - }); - }); - - describe('with 4 labels and `num-labels="2"` attribute', function() { - beforeEach(async function() { - element = await createFixture(html` - - Label 1 - Label 2 - Label 3 - Label 4 - - `); - }); - - it('only 2 labels should be visible', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(2); - }); - }); - - describe('with 4 labels and `num-labels="4"` attribute', function() { - beforeEach(async function() { - element = await createFixture(html` - - Label 1 - Label 2 - Label 3 - Label 4 - - `); - }); - - it('all 4 labels should be visible', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(4); - }); - }); - - describe('with category', function() { - beforeEach(async function() { - element = await createFixture(html` - - Group - Label 1 - Label 2 - - `); - }); - - it('should display the category text', async function() { - const snapshot = await a11ySnapshot(); - expect(snapshot.children?.some(x => x.name === 'Group')).to.be.true; - }); - }); -}); diff --git a/elements/pf-label/README.md b/elements/pf-label/README.md deleted file mode 100644 index c349b4da7e..0000000000 --- a/elements/pf-label/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Label -An inline-block element component that provides a distinct visual style for -metadata in a UI. Commonly used as visual representations for tags, labels can -include an optional pf-icon and are available in a solid and outline style -variant. - -Read more about Label in the [PatternFly Elements Label documentation](https://patternflyelements.org/components/label) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-label/pf-label.js'; -``` - -## Usage - -Default -```html -

Some Text Your label text here

-``` - -Color Options: `grey (default), blue ,green, orange, red, purple, cyan` -```html -

Some Text Your label text here

-``` - -With and Icon: -```html -

Some Text Your label text here

-``` - -Outline variant: -```html -

Some Text Your label text here

-``` - -> ### Conveying meaning to assistive technologies -> Using color to add meaning only provides a visual indication, which will not be conveyed to users of assistive technologies – such as screen readers. Ensure that -> information denoted by the color is either obvious from the content itself (e.g. the visible text), or is included through alternative means, such as additional text -> hidden with the a class. - - -```html - - Some text Some additional info - -``` diff --git a/elements/pf-label/demo/compact.html b/elements/pf-label/demo/compact.html deleted file mode 100644 index a1525205a2..0000000000 --- a/elements/pf-label/demo/compact.html +++ /dev/null @@ -1,21 +0,0 @@ -
- Default - Blue label - Green label - Orange label - Red label - Purple label - Cyan label - Gold label - Grey label -
- - - - diff --git a/elements/pf-label/demo/filled-color.html b/elements/pf-label/demo/filled-color.html deleted file mode 100644 index 74adde4305..0000000000 --- a/elements/pf-label/demo/filled-color.html +++ /dev/null @@ -1,32 +0,0 @@ -
- Blue - Green - Orange - Red Hat - Purple - Cyan - Gold - Grey -
- - - - diff --git a/elements/pf-label/demo/icon.html b/elements/pf-label/demo/icon.html deleted file mode 100644 index 9a08797f2f..0000000000 --- a/elements/pf-label/demo/icon.html +++ /dev/null @@ -1,33 +0,0 @@ -
-
- Default - Blue label - Green label - Orange label - Red label - Purple label - Cyan label - Gold label -
- -
- Default - Blue label - Green label - Orange label - Red label - Purple label - Cyan label - Gold label -
-
- - - - diff --git a/elements/pf-label/demo/index.html b/elements/pf-label/demo/index.html deleted file mode 100644 index 9050ebf7f6..0000000000 --- a/elements/pf-label/demo/index.html +++ /dev/null @@ -1,13 +0,0 @@ -
- Default -
- - - - diff --git a/elements/pf-label/demo/link.html b/elements/pf-label/demo/link.html deleted file mode 100644 index 4eb4c4f070..0000000000 --- a/elements/pf-label/demo/link.html +++ /dev/null @@ -1,14 +0,0 @@ -
- Blue - Green - Orange - Red Hat - Purple - Cyan - Gold - Grey -
- - diff --git a/elements/pf-label/demo/outline-color.html b/elements/pf-label/demo/outline-color.html deleted file mode 100644 index fe29dfd72c..0000000000 --- a/elements/pf-label/demo/outline-color.html +++ /dev/null @@ -1,20 +0,0 @@ -
- Blue - Green - Orange - Red - Purple - Cyan - Gold - Grey -
- - - - diff --git a/elements/pf-label/demo/patternfly.html b/elements/pf-label/demo/patternfly.html deleted file mode 100644 index 3404deabdb..0000000000 --- a/elements/pf-label/demo/patternfly.html +++ /dev/null @@ -1,200 +0,0 @@ -
-

Filled

- Grey - Grey icon - Grey removable - Grey icon removable - Grey link - Grey link removable - Grey label with icon that overflows -
- -
-

Blue

- Blue - Blue icon - Blue removable - Blue icon removable - Blue link - Blue link removable - Blue label with icon that overflows -
- -
-

Green

- Green - Green icon - Green removable - Green icon removable - Green link - Green link removable - Green label with icon that overflows -
- -
-

Orange

- Orange - Orange icon - Orange removable - Orange icon removable - Orange link - Orange link removable - Orange label with icon that overflows -
- -
-

Red

- Red - Red icon - Red removable - Red icon removable - Red link - Red link removable - Red label with icon that overflows -
- -
-

Purple

- Purple - Purple icon - Purple removable - Purple icon removable - Purple link - Purple link removable - Purple label with icon that overflows -
- -
-

Cyan

- Cyan - Cyan icon - Cyan removable - Cyan icon removable - Cyan link - Cyan link removable - Cyan label with icon that overflows -
- -
-

Gold

- Gold - Gold icon - Gold removable - Gold icon removable - Gold link - Gold link removable - Gold label with icon that overflows -
- -
-

Outline

- Grey - Grey icon - Grey removable - Grey icon removable - Grey link - Grey link removable - Grey label with icon that overflows -
- -
-

Outline Blue

- Blue - Blue icon - Blue removable - Blue icon removable - Blue link - Blue link removable - Blue label with icon that overflows -
- -
-

Outline Green

- Green - Green icon - Green removable - Green icon removable - Green link - Green link removable - Green label with icon that overflows -
- -
-

Outline Orange

- Orange - Orange icon - Orange removable - Orange icon removable - Orange link - Orange link removable - Orange label with icon that overflows -
- -
-

Outline Red

- Red - Red icon - Red removable - Red icon removable - Red link - Red link removable - Red label with icon that overflows -
- -
-

Outline Purple

- Purple - Purple icon - Purple removable - Purple icon removable - Purple link - Purple link removable - Purple label with icon that overflows -
- -
-

Outline Cyan

- Cyan - Cyan icon - Cyan removable - Cyan icon removable - Cyan link - Cyan link removable - Cyan label with icon that overflows -
- -
-

Outline Gold

- Gold - Gold icon - Gold removable - Gold icon removable - Gold link - Gold link removable - Gold label with icon that overflows -
- -
-

Outline Compact

- Gold - Gold icon - Gold removable - Gold icon removable - Gold link - Gold link removable - Gold label with icon that overflows -
- - - - diff --git a/elements/pf-label/docs/pf-label.md b/elements/pf-label/docs/pf-label.md deleted file mode 100644 index 34c7ae8423..0000000000 --- a/elements/pf-label/docs/pf-label.md +++ /dev/null @@ -1,129 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - Default -{% endrenderOverview %} - -{% band header="Usage" %} - ### Default - {% htmlexample %} - Default - {% endhtmlexample %} - - ### With a color - Available colors are: grey (default), blue, green, orange, red, purple, cyan, gold - - {% htmlexample %} - Blue - Green - Orange - Red - Purple - Cyan - Gold - {% endhtmlexample %} - -
- - ### Conveying meaning to assistive technologies - Using color to add meaning only provides a visual indication, which will not be conveyed to users of assistive technologies – such as screen readers. Ensure that information denoted by the color is either obvious from the content itself (e.g. the visible text), or is included through alternative means, such as additional text hidden with a class. - -
- - {% htmlexample %} - - - Red Warning - - {% endhtmlexample %} - - ### Outline variant - Swaps the color style for a outline styled variant - - {% htmlexample %} - Blue - Green - Orange - Red - Purple - Cyan - Gold - Default - {% endhtmlexample %} - - ### With icon as an attribute - Adds a optional fixed size `pf-icon` to the label as a prefix - - {% htmlexample %} - Globe - {% endhtmlexample %} - - Read more about Icon in the [PatternFly Elements Icon documentation](https://patternflyelements.org/components/icon) - - ### With user defined image using `slot="icon"` - Adds user defined SVG or `pf-icon` to the label. - - {% htmlexample %} - Globe - - - {% endhtmlexample %} - - {% htmlexample %} - Globe - - - {% endhtmlexample %} - - ### Compact style - Creates a compact style label which can be combined with color, variant and icon - - {% htmlexample %} - Globe - - - Green - Orange - Red - Purple - Cyan - Gold - Default - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-label/pf-label.css b/elements/pf-label/pf-label.css deleted file mode 100644 index 4861072d2a..0000000000 --- a/elements/pf-label/pf-label.css +++ /dev/null @@ -1,320 +0,0 @@ -:host { - position: relative; - white-space: nowrap; - border: 0; - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--200, #8a8d90)); -} - -pf-icon, ::slotted(pf-icon) { - color: currentColor; -} - -:host, -#container { - display: inline-flex; - align-items: center; - vertical-align: middle; -} - -#container { - overflow: clip; - overflow-clip-margin: 6px; - text-overflow: ellipsis; - white-space: nowrap; - border-width: 0; - /** label top padding */ - padding-top: var(--pf-c-label--PaddingTop, var(--pf-global--spacer--xs, 0.25rem)); - /** label left padding */ - padding-left: var(--pf-c-label--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem)); - /** label bottom padding */ - padding-bottom: var(--pf-c-label--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem)); - /** label right padding */ - padding-right: var(--pf-c-label--PaddingRight, var(--pf-global--spacer--sm, 0.5rem)); - /** label font size */ - font-size: var(--pf-c-label--FontSize, var(--pf-global--FontSize--sm, 0.875rem)); - /** label text color */ - color: var(--pf-c-label--Color, var(--pf-global--Color--100, #151515)); - /** label background color */ - background-color: var(--pf-c-label--BackgroundColor, var(--pf-global--palette--black-150, #f5f5f5)); - /** label border radius */ - border-radius: var(--pf-c-label--BorderRadius, 30em); - /** label content max width */ - max-width: var(--pf-c-label__content--MaxWidth, 100%); - /** label content text color */ - color: var(--pf-c-label__content--Color, var(--pf-global--Color--100, #151515)); - - --pf-global--icon--FontSize--sm: 14px; -} - -#container::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - content: ""; - border-radius: var(--pf-c-label--BorderRadius, 30em); - /** label content border width and color */ - border: var(--pf-c-label__content--before--BorderWidth, 1px) solid var(--pf-c-label__content--before--BorderColor, var(--pf-global--palette--black-300, #d2d2d2)); -} - -[part=icon] { - display: none; - pointer-events: none; -} - -.hasIcon [part=icon] { - display: inline-flex; - width: 1em; -} - -.compact { - /** compact label top padding */ - --pf-c-label--PaddingTop: var(--pf-c-label--m-compact--PaddingTop, 0); - /** compact label right padding */ - --pf-c-label--PaddingRight: var(--pf-c-label--m-compact--PaddingRight, var(--pf-global--spacer--sm, 0.5rem)); - /** compact label bottom padding */ - --pf-c-label--PaddingBottom: var(--pf-c-label--m-compact--PaddingBottom, 0); - /** compact label left padding */ - --pf-c-label--PaddingLeft: var(--pf-c-label--m-compact--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem)); - --pf-global--icon--FontSize--sm: 12px; -} - -.outline { - /** outline label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-outline--BackgroundColor, #ffffff); - --pf-c-label__content--before--BorderColor: var(--pf-global--palette--black-300, #d2d2d2); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2)); -} - -.blue { - /** blue label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-blue__content--Color, var(--pf-global--info-color--200, #002952)); - /** blue label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-blue--BackgroundColor, var(--pf-global--palette--blue-50, #e7f1fa)); - /** blue label content border color */ - --pf-c-label__content--before--BorderColor: var(--pf-c-label--m-blue__content--before--BorderColor, var(--pf-global--palette--blue-100, #bee1f4)); - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-c-label--m-blue__content--link--hover--before--BorderColor, - var(--pf-global--primary-color--100, #06c))); - - --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-blue__icon--Color, var(--pf-global--primary-color--100, #06c))); -} - -.blue.outline { - /** outline blue label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-blue__content--Color, var(--pf-global--primary-color--100, #06c))); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-c-label--m-outline--m-blue__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2))); -} - -.cyan { - /** cyan label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-cyan__content--Color, var(--pf-global--default-color--300, #003737)); - /** cyan label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-cyan--BackgroundColor, var(--pf-global--palette--cyan-50, #f2f9f9)); - /** cyan label content border color */ - --pf-c-label__content--before--BorderColor: var(--pf-c-label--m-cyan__content--before--BorderColor, var(--pf-global--palette--cyan-100, #a2d9d9)); - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-c-label--m-cyan__content--link--hover--before--BorderColor, - var(--pf-global--default-color--200, #009596))); - - --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-cyan__icon--Color, var(--pf-global--default-color--200, #009596))); -} - -.cyan.outline { - /** outline cyan label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-cyan__content--Color, var(--pf-global--palette--cyan-400, #005f60))); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-c-label--m-outline--m-cyan__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2))); -} - -.green { - /** green label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-green__content--Color,var(--pf-global--success-color--200, #1e4f18)); - /** green label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-green--BackgroundColor, var(--pf-global--palette--green-50, #f3faf2)); - /** green label content border color */ - --pf-c-label__content--before--BorderColor: var(--pf-c-label--m-green__content--before--BorderColor, var(--pf-global--palette--green-100, #bde5b8)); - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-c-label--m-green__content--link--hover--before--BorderColor, - var(--pf-global--success-color--100, #3e8635))); - - --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-green__icon--Color, var(--pf-global--success-color--100, #3e8635))); -} - -.green.outline { - /** outline green label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-green__content--Color, var(--pf-global--success-color--100, #3e8635))); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-c-label--m-outline--m-green__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2))); -} - -.orange { - /** orange label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-orange__content--Color, var(--pf-global--palette--orange-700, #3b1f00)); - /** orange label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-orange--BackgroundColor, var(--pf-global--palette--orange-50, #fff6ec)); - /** orange label content border color */ - --pf-c-label__content--before--BorderColor: var(--pf-c-label--m-orange__content--before--BorderColor, var(--pf-global--palette--orange-100, #f4b678)); - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-c-label--m-orange__content--link--hover--before--BorderColor, - var(--pf-global--palette--orange-300, #ec7a08))); - - --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-orange__icon--Color, var(--pf-global--palette--orange-300, #ec7a08))); -} - -.orange.outline { - /** outline orange label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-orange__content--Color, var(--pf-global--palette--orange-500, #8f4700))); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-c-label--m-outline--m-orange__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2))); -} - -.purple { - /** purple label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-purple__content--Color, var(--pf-global--palette--purple-700, #1f0066)); - /** purple label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-purple--BackgroundColor, var(--pf-global--palette--purple-50, #f2f0fc)); - /** purple label content border color */ - --pf-c-label__content--before--BorderColor: var(--pf-c-label--m-purple__content--before--BorderColor, var(--pf-global--palette--purple-100, #cbc1ff)); - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-c-label--m-purple__content--link--hover--before--BorderColor, - var(--pf-global--palette--purple-500, #6753ac))); - - --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-purple__icon--Color, var(--pf-global--palette--purple-500, #6753ac))); -} - -.purple.outline { - /** outline purple label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-purple__content--Color, var(--pf-global--palette--purple-500, #6753ac))); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-c-label--m-outline--m-purple__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2))); -} - -.red { - /** red label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-red__content--Color, var(--pf-global--palette--red-300, #7d1007)); - /** red label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-red--BackgroundColor, var(--pf-global--palette--red-50, #faeae8)); - /** red label content border color */ - --pf-c-label__content--before--BorderColor: var(--pf-c-label--m-red__content--before--BorderColor, var(--pf-global--palette--red-100, #c9190b)); - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-c-label--m-red__content--link--hover--before--BorderColor, - var(--pf-global--danger-color--100, #c9190b))); - - --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-red__icon--Color, var(--pf-global--danger-color--100, #c9190b))); -} - -.red.outline { - /** outline red label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-red__content--Color, var(--pf-global--danger-color--100, #c9190b))); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-c-label--m-outline--m-red__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2))); -} - -.gold { - /** gold label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-gold__content--Color, var(--pf-global--palette--gold-700, #3d2c00)); - /** gold label background color */ - --pf-c-label--BackgroundColor: var(--pf-c-label--m-gold--BackgroundColor, var(--pf-global--palette--gold-50, #fdf7e7)); - /** gold label content border color */ - --pf-c-label__content--before--BorderColor: var(--pf-c-label--m-gold__content--before--BorderColor, var(--pf-global--palette--gold-100, #f9e0a2)); - - --_label-link-hover-border-color: - var(--pf-c-label__content--link--hover--before--BorderColor, - var(--pf-c-label--m-gold__content--link--hover--before--BorderColor, - var(--pf-global--palette--gold-300, #f4c145))); - - --_label-icon-color: var(--pf-c-label__icon--Color, var(--pf-c-label--m-gold__icon--Color, var(--pf-global--palette--gold-400, #f0ab00))); -} - -.gold.outline { - /** outline gold label content text color */ - --pf-c-label__content--Color: var(--pf-c-label--m-outline__content--Color, var(--pf-c-label--m-outline--m-gold__content--Color, var(--pf-global--palette--gold-600, #795600))); - - --_label-link-hover-border-color: - var(--pf-c-label--m-outline__content--link--hover--before--BorderColor, - var(--pf-c-label--m-outline--m-gold__content--link--hover--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2))); -} - -.hasIcon [part=icon] { - left: var(--pf-c-label--PaddingLeft, var(--pf-global--spacer--md, 1rem)); - margin-inline-end: var(--pf-c-label__icon--MarginRight, var(--pf-global--spacer--xs, 0.25rem)); - /** label icon color */ - color: var(--_label-icon-color); -} - -pf-button { - --pf-c-button--FontSize: var(--pf-c-label__c-button--FontSize, - var(--pf-global--FontSize--xs, 0.75rem)); - --pf-c-button--PaddingTop: var(--pf-c-label__c-button--PaddingTop, - var(--pf-global--spacer--xs, 0.25rem)); - --pf-c-button--PaddingRight: var(--pf-c-label__c-button--PaddingRight, - var(--pf-global--spacer--sm, 0.5rem)); - --pf-c-button--PaddingBottom: var(--pf-c-label__c-button--PaddingBottom, - var(--pf-global--spacer--xs, 0.25rem)); - --pf-c-button--PaddingLeft: var(--pf-c-label__c-button--PaddingLeft, - var(--pf-global--spacer--sm, 0.5rem)); - margin-top: var(--pf-c-label__c-button--MarginTop, -0.5rem); - margin-right: var(--pf-c-label__c-button--MarginRight, -0.5rem); - margin-bottom: var(--pf-c-label__c-button--MarginBottom, -0.5rem); - margin-left: var(--pf-c-label__c-button--MarginLeft, 0.25rem); -} - -#link { - color: inherit; - text-decoration: none; - cursor: pointer; -} - -#container:has(#link:hover)::before, -#container:has(#link:focus)::before { - border-width: var(--pf-c-label__content--link--hover--before--BorderWidth, 2px); - border-color: var(--_label-link-hover-border-color); -} - -svg { - vertical-align:-0.125em; - fill: currentColor; - height: 1em; - width: 1em; -} diff --git a/elements/pf-label/pf-label.ts b/elements/pf-label/pf-label.ts deleted file mode 100644 index 52462efa95..0000000000 --- a/elements/pf-label/pf-label.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { LitElement, html, isServer, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import '@patternfly/elements/pf-button/pf-button.js'; - -import styles from './pf-label.css'; - -export class LabelCloseEvent extends Event { - constructor() { - super('close', { bubbles: true, cancelable: true }); - } -} - -/** - * The **label** component allows users to add specific element captions for user - * clarity and convenience. - * @summary Allows users to display meta data in a stylized form. - * @alias Label - * @fires {LabelCloseEvent} close - when a removable label's close button is clicked - */ -@customElement('pf-label') -export class PfLabel extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** - * Changes the style of the label. - * - Filled: Colored background with colored border. - * - Outline: White background with colored border. - */ - @property() variant: - | 'filled' - | 'outline' = 'filled'; - - /** - * Changes the color of the label - */ - @property() color: - | 'blue' - | 'cyan' - | 'green' - | 'orange' - | 'purple' - | 'red' - | 'grey' - | 'gold' = 'grey'; - - /** Shorthand for the `icon` slot, the value is icon name */ - @property() icon?: string; - - /** Flag indicating the label is compact */ - @property({ type: Boolean }) compact = false; - - /** Flag indicating the label text should be truncated */ - @property({ type: Boolean }) truncated = false; - - /** Flag indicating the label is removable */ - @property({ type: Boolean }) removable = false; - - /** Text label for a removable label's close button */ - @property({ attribute: 'close-button-label' }) closeButtonLabel?: string; - - /** When set, the label becomes a link. The label text renders inside an anchor element. */ - @property({ reflect: true }) href?: string; - - /** Represents the state of the anonymous and icon slots */ - #slots = new SlotController(this, null, 'icon'); - - override connectedCallback(): void { - super.connectedCallback(); - if (isServer) { - this.dispatchEvent(new Event('ssr:label', { bubbles: true })); - } - } - - override render(): TemplateResult<1> { - const { compact, truncated, href } = this; - const { variant, color, icon } = this; - const hasIcon = !!icon || this.#slots.hasSlotted('icon'); - const isLink = !!href; - return html` - - - - - - - ${isLink ? html` - - ` : html` - - `} - - - - - - - - - - `; - } - - #onClickClose() { - if (this.removable && this.dispatchEvent(new LabelCloseEvent())) { - this.remove(); - } - } -} - -export type LabelVariant = PfLabel['variant']; - -export type LabelColor = PfLabel['color']; - -declare global { - interface HTMLElementTagNameMap { - 'pf-label': PfLabel; - } -} diff --git a/elements/pf-label/test/pf-label.spec.ts b/elements/pf-label/test/pf-label.spec.ts deleted file mode 100644 index 99c5ff26bb..0000000000 --- a/elements/pf-label/test/pf-label.spec.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { getColor, hexToRgb } from '@patternfly/pfe-tools/test/hex-to-rgb.js'; -import { PfLabel } from '@patternfly/elements/pf-label/pf-label.js'; -import { PfIcon } from '@patternfly/elements/pf-icon/pf-icon.js'; - -import '@patternfly/pfe-tools/test/stub-logger.js'; - -const example = html` - Default -`; - -const exampleWithColorAttribute = html` - Red -`; - -const exampleWithColorAttributeEmpty = html` - Default -`; - -const exampleWithOutlineAttribute = html` - Default Outline -`; - -const exampleWithCompactAttribute = html` - Default Compact -`; - -const exampleWithHref = html` - Link Label -`; - - -describe('', function() { - before(function() { - // replace the default built-in icon set resolveIconName function - // with one that loads local icons. we don't want tests dependent on - // prod servers. - PfIcon.addIconSet('rh', function(name: string) { - return new URL(`/elements/pf-icon/test/${name.replace('rh', 'rh-icon')}.svg`); - }); - }); - - it('imperatively instantiates', function() { - expect(document.createElement('pf-label')).to.be.an.instanceof(PfLabel); - }); - - it('should upgrade', async function() { - const el = await createFixture(example); - const klass = customElements.get('pf-label'); - expect(el) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfLabel); - }); - - it('should display default variant', async function() { - const el = await createFixture(example); - await el.updateComplete; - const container = el.shadowRoot!.querySelector('#container')!; - const beforeStyles = getComputedStyle(container, '::before'); - - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#f5f5f5')); - expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); - }); - - it('should display color variant if color attribute is present', async function() { - const el = await createFixture(exampleWithColorAttribute); - await el.updateComplete; - const container = el.shadowRoot!.querySelector('#container')!; - const beforeStyles = getComputedStyle(container, '::before'); - - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#7d1007')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#faeae8')); - expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(201, 25, 11)'); - }); - - it('should display default variant if color attribute is present but empty', async function() { - const el = await createFixture( exampleWithColorAttributeEmpty); - await el.updateComplete; - const container = el.shadowRoot!.querySelector('#container')!; - const beforeStyles = getComputedStyle(container, '::before'); - - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#f5f5f5')); - expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); - }); - - it('should display outline variant if the attribute outline is present', async function() { - const el = await createFixture(exampleWithOutlineAttribute); - await el.updateComplete; - const container = el.shadowRoot!.querySelector('#container')!; - const beforeStyles = getComputedStyle(container, '::before'); - - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#ffffff')); - expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); - }); - - describe('with valid icon attribute', function() { - let element: PfLabel; - beforeEach(async function() { - element = await createFixture(html` - Default Icon - `); - element.updateComplete; - }); - it('should render a pf-icon', async function() { - const icon = element.shadowRoot!.querySelector('pf-icon')!; - expect(icon.hidden).to.be.false; - expect(icon.icon).to.equal(element.icon); - }); - }); - - describe('with empty icon attribute', function() { - let element: PfLabel; - beforeEach(async function() { - element = await createFixture(html` - Default - `); - element.updateComplete; - }); - it('should not render a pf-icon', async function() { - const icon = element.shadowRoot!.querySelector('pf-icon')!; - expect(icon.hidden).to.be.true; - expect(icon.icon).to.be.undefined; - }); - }); - - it('should display compact version if the attribute is-compact is present', async function() { - const el = await createFixture(exampleWithCompactAttribute); - await el.updateComplete; - const container = el.shadowRoot!.querySelector('#container')!; - const beforeStyles = getComputedStyle(container, '::before'); - const containerStyles = getComputedStyle(container); - - expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); - expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#f5f5f5')); - expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); - - expect(containerStyles.getPropertyValue('padding-top')).to.equal('0px'); - expect(containerStyles.getPropertyValue('padding-right')).to.equal('8px'); // 0.5rem = 8px @ 16px browser default - expect(containerStyles.getPropertyValue('padding-bottom')).to.equal('0px'); - expect(containerStyles.getPropertyValue('padding-left')).to.equal('8px'); // 0.5rem = 8px @ 16px browser default - }); - - describe('with href attribute', function() { - let element: PfLabel; - beforeEach(async function() { - element = await createFixture(exampleWithHref); - await element.updateComplete; - }); - - it('should render an anchor element', function() { - const link = element.shadowRoot!.querySelector('#link'); - expect(link).to.be.an.instanceOf(HTMLAnchorElement); - }); - - it('should set the href on the anchor', function() { - const link = element.shadowRoot!.querySelector('#link') as HTMLAnchorElement; - expect(link.href).to.equal('https://example.com/'); - }); - - it('should not render an anchor when href is not set', async function() { - const el = await createFixture(example); - await el.updateComplete; - const link = el.shadowRoot!.querySelector('#link'); - expect(link).to.be.null; - }); - }); -}); diff --git a/elements/pf-modal/README.md b/elements/pf-modal/README.md deleted file mode 100644 index f8ec3b3459..0000000000 --- a/elements/pf-modal/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# PatternFly Elements Modal - -`pf-modal` is a self-contained modal window that is hidden on page load and (when activated) restricts the user from interacting with content in the main window. - -Read more about Modal in the [PatternFly Elements Modal documentation](https://patternflyelements.org/components/modal) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-modal/pf-modal.js'; -``` - -## Usage - -Open a modal dialog with the `showModal()` method, or by setting the `open` boolean attribute. - -```html - -

Modal with a header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- Learn more -
- - -``` - -### With a trigger - -You may assign a button-like trigger element to the modal by setting the modal element's `trigger` attribute to the trigger's ID. - -```html - -

Modal with a header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- Learn more -
- - -``` - -You may also imperatively set the trigger element with the `setTrigger()` method: -```js -const modal = document.querySelector('pf-modal'); -const trigger = document.querySelector('button#my-trigger'); -modal.setTrigger(trigger); -``` - diff --git a/elements/pf-modal/demo/custom-header-footer.html b/elements/pf-modal/demo/custom-header-footer.html deleted file mode 100644 index 8701e84c4f..0000000000 --- a/elements/pf-modal/demo/custom-header-footer.html +++ /dev/null @@ -1,59 +0,0 @@ -
- -

With custom header/footer content.

-

Allows for custom content in the header and/or footer by slotting HTML.

-

When static text describing the modal is available, it can be wrapped - with an ID referring to the - modal's aria-describedby value.

-

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis - aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint - occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-

- - Custom modal footer. -

-
- Show modal -
- - - - diff --git a/elements/pf-modal/demo/custom-icon.html b/elements/pf-modal/demo/custom-icon.html deleted file mode 100644 index 27189df3e0..0000000000 --- a/elements/pf-modal/demo/custom-icon.html +++ /dev/null @@ -1,54 +0,0 @@ -
- -

- - Modal Header -

-

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis - aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint - occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
- Show modal -
- - - - diff --git a/elements/pf-modal/demo/index.html b/elements/pf-modal/demo/index.html deleted file mode 100644 index 9574c1cad7..0000000000 --- a/elements/pf-modal/demo/index.html +++ /dev/null @@ -1,55 +0,0 @@ -
- -

Simple modal header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum.

- Confirm - Cancel -
- Show modal -
- - - - diff --git a/elements/pf-modal/demo/no-header.html b/elements/pf-modal/demo/no-header.html deleted file mode 100644 index 88393b031c..0000000000 --- a/elements/pf-modal/demo/no-header.html +++ /dev/null @@ -1,49 +0,0 @@ -
- -

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis - aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint - occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
- Show modal -
- - - - diff --git a/elements/pf-modal/demo/styling.html b/elements/pf-modal/demo/styling.html deleted file mode 100644 index 1b5bfaa3bf..0000000000 --- a/elements/pf-modal/demo/styling.html +++ /dev/null @@ -1,56 +0,0 @@ -
-

--pf-c-modal-box--Width CSS Custom Property

- -

Width 50% header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum.

- Confirm - Cancel -
- Show modal -
- - - - diff --git a/elements/pf-modal/demo/top-aligned.html b/elements/pf-modal/demo/top-aligned.html deleted file mode 100644 index 32ec70ae0c..0000000000 --- a/elements/pf-modal/demo/top-aligned.html +++ /dev/null @@ -1,56 +0,0 @@ -
-

Top aligned

- -

Top modal header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum.

- Confirm - Cancel -
- Show modal -
- - - - diff --git a/elements/pf-modal/demo/trigger-attribute.html b/elements/pf-modal/demo/trigger-attribute.html deleted file mode 100644 index e26fe0e851..0000000000 --- a/elements/pf-modal/demo/trigger-attribute.html +++ /dev/null @@ -1,60 +0,0 @@ -
-

You may set an external button as the trigger by setting its' ID as the modal's trigger attribute.

- -

External trigger

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum.

- Confirm - Cancel -
- -

Arbitrary content can intervene between the external trigger and the modal element, as long as they are within the - same root.

- - Show modal with external trigger -
- - - - diff --git a/elements/pf-modal/demo/variants.html b/elements/pf-modal/demo/variants.html deleted file mode 100644 index 1b89ddea75..0000000000 --- a/elements/pf-modal/demo/variants.html +++ /dev/null @@ -1,86 +0,0 @@ -
-

Small

- -

Small modal header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum.

- Confirm - Cancel -
- Show modal -
- -
-

Medium

- -

Medium modal header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum.

- Confirm - Cancel -
- Show modal -
- -
-

Large

- -

Large modal header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut - aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu - fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit - anim id est laborum.

- Confirm - Cancel -
- Show modal -
- - - - diff --git a/elements/pf-modal/demo/warning-alert.html b/elements/pf-modal/demo/warning-alert.html deleted file mode 100644 index f2a55e6ed1..0000000000 --- a/elements/pf-modal/demo/warning-alert.html +++ /dev/null @@ -1,54 +0,0 @@ -
- -

- - Modal Header -

-

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis - aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint - occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
- Show modal -
- - - - diff --git a/elements/pf-modal/docs/pf-modal.md b/elements/pf-modal/docs/pf-modal.md deleted file mode 100644 index 09e5685a97..0000000000 --- a/elements/pf-modal/docs/pf-modal.md +++ /dev/null @@ -1,62 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - -

Modal with a header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- Learn more -
- Open modal -{% endrenderOverview %} - -{% band header="Usage" %} - {% htmlexample %} - -

Modal with a header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- Learn more -
- Open modal - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %} - {% htmlexample %} - -

Small modal with a header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- Learn more -
- Open a small modal - {% endhtmlexample %} - - {% htmlexample %} - -

Medium modal with a header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- Learn more -
- Open a medium modal - {% endhtmlexample %} - - {% htmlexample %} - -

Large modal with a header

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- Learn more -
- Open a large modal - {% endhtmlexample %} -{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-modal/pf-modal.css b/elements/pf-modal/pf-modal.css deleted file mode 100644 index df415d3880..0000000000 --- a/elements/pf-modal/pf-modal.css +++ /dev/null @@ -1,195 +0,0 @@ -:host { - display: block; - position: relative; - --_spacer-align-top: var(--pf-c-modal-box--m-align-top--spacer, - var(--pf-global--spacer--sm, 0.5rem)); - --_height-offset: min(var(--_spacer-align-top), var(--pf-global--spacer--2xl, 3rem)); -} - -[hidden] { - display: none !important; -} - -section { - display: flex; - position: fixed; - height: 100%; - width: 100%; - top: 0; - left: 0; - align-items: center; - justify-content: center; - /** Z-index for modal box */ - z-index: var(--pf-c-modal-box--ZIndex, - var(--pf-global--ZIndex--xl, - 500)); -} - -#container { - position: relative; - max-height: inherit; -} - -[part=overlay] { - position: fixed; - height: 100%; - width: 100%; - top: 0; - left: 0; - background-color: var(--pf-c-backdrop--BackgroundColor, - var(--pf-global--BackgroundColor--dark-transparent-100, - rgba(3, 3, 3, 0.62))); -} - -[part=dialog] { - position: relative; - margin: 0 auto; - - /** Width of modal */ - width: var(--pf-c-modal-box--Width, - calc(100% - var(--pf-global--spacer--xl,2rem))); - - /** Max width of modal */ - max-width: var(--pf-c-modal-box--MaxWidth, - calc(100% - var(--pf-global--spacer--xl,2rem))); - - /** Max height of modal */ - max-height: var(--pf-c-modal-box--MaxHeight, - calc(100% - var(--pf-global--spacer--2xl,3rem))); - - /** Box shadow for modal */ - box-shadow: var(--pf-c-modal-box--BoxShadow, - var(--pf-global--BoxShadow--xl, - 0 1rem 2rem 0 rgba(3, 3, 3, 0.16), - 0 0 0.5rem 0 rgba(3, 3, 3, 0.1))); - - /** Background color for modal */ - background-color: var(--pf-c-modal-box--BackgroundColor, - var(--pf-global--BackgroundColor--100, - var(--pf-global--BackgroundColor--100, #fff))); - - padding: var(--pf-global--spacer--lg, 1.5rem); - margin-inline: var(--pf-global--spacer--md, 1rem); -} - -:host([width]) [part=dialog], -:host([variant]) [part=dialog] { - margin-inline: 0; -} - -:host([width="small"]) [part=dialog], -:host([variant="small"]) [part=dialog] { - /** Max width for small modal */ - --pf-c-modal-box--Width: var(--pf-c-modal-box--m-sm--sm--MaxWidth, 35rem); -} - -:host([width="medium"]) [part=dialog], -:host([variant="medium"]) [part=dialog] { - /** Max width for medium modal */ - --pf-c-modal-box--Width: var(--pf-c-modal-box--m-md--Width, 52.5rem); -} - -:host([width="large"]) [part=dialog], -:host([variant="large"]) [part=dialog] { - /** Max width for large modal */ - --pf-c-modal-box--Width: var(--pf-c-modal-box--m-lg--lg--MaxWidth, 70rem); -} - -[part=content] { - overflow-y: auto; - overscroll-behavior: contain; - - max-height: var(--pf-c-modal-box--MaxHeight, - calc(100vh - var(--pf-global--spacer--2xl, - 3rem))); - - box-sizing: border-box; -} - -[part=content] ::slotted([slot="header"]) { - margin-top: 0 !important; -} - -header { - position: sticky; - top: 0; - background-color: var(--pf-c-modal-box--BackgroundColor, - var(--pf-global--BackgroundColor--100, - var(--pf-global--BackgroundColor--100, - #fff))); -} - -header ::slotted(:is(h1,h2,h3,h4,h5,h6)[slot="header"]) { - /** Font size for modal title */ - font-size: var(--pf-c-modal-box__title--FontSize, - var(--pf-global--FontSize--2xl, - 1.5rem)); - font-weight: var(--pf-global--FontWeight--normal, 400); - /** Font family for modal title */ - font-family: var(--pf-c-modal-box__title--FontFamily, - var(--pf-global--FontFamily--heading--sans-serif, - var(--pf-global--FontFamily--redhat-updated--heading--sans-serif, - "RedHatDisplayUpdated", "Overpass", overpass, helvetica, arial, sans-serif))); -} - -[part=close-button] { - background-color: transparent; - border: none; - margin: 0; - padding: 0; - text-align: left; - - position: absolute; - cursor: pointer; - line-height: 24px; - padding-block: - var(--pf-c-button--PaddingTop, - var(--pf-global--spacer--form-element, 0.375rem)); - padding-inline: - var(--pf-c-button--PaddingRight, - var(--pf-global--spacer--md, 1rem)); - top: 0; - right: calc(var(--pf-global--spacer--lg, 1.5rem) / -3); - color: var(--pf-c-button--m-plain--Color, - var(--pf-global--Color--200, - #6a6e73)); - font-size: var(--pf-c-button--FontSize, - var(--pf-global--FontSize--md, - 1rem)); -} - -[part=close-button]:is(:focus-within, :focus-visible, :hover) { - color: var(--pf-c-button--m-plain--focus--Color, var(--pf-global--Color--100, #151515)); -} - -[part=close-button] > svg { - font-size: 16px; - width: var(--pf-global--spacer--md, 1rem); - aspect-ratio: 1/1; -} - -:host([position="top"]) #dialog { - align-self: start; - - margin-block: var(--pf-c-modal-box--m-align-top--MarginTop, - var(--pf-c-modal-box--m-align-top--spacer, - 2rem)); - - margin-inline: var(--pf-global--spacer--md, 1rem); - - width: 100%; - - max-width: var(--pf-c-modal-box--m-align-top--MaxWidth, - calc(100% - min( - var(--pf-c-modal-box--m-align-top--spacer, 2rem) * 2, - var(--pf-global--spacer--xl, 2rem)))); - - max-height: var(--pf-c-modal-box--m-align-top--MaxHeight, - calc(100% - var(--_height-offset) - var(--_spacer-align-top))); -} - -footer { - display: flex; - align-items: center; - gap: var(--pf-global--spacer--xl, 0.5rem); -} diff --git a/elements/pf-modal/pf-modal.ts b/elements/pf-modal/pf-modal.ts deleted file mode 100644 index ae7024cfe2..0000000000 --- a/elements/pf-modal/pf-modal.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { query } from 'lit/decorators/query.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { ComposedEvent } from '@patternfly/pfe-core'; -import { bound, initializer, observes } from '@patternfly/pfe-core/decorators.js'; -import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import style from './pf-modal.css'; - -export class ModalCancelEvent extends ComposedEvent { - constructor() { - super('cancel'); - } -} - -export class ModalCloseEvent extends ComposedEvent { - constructor() { - super('close'); - } -} - -export class ModalOpenEvent extends ComposedEvent { - constructor( - /** The trigger element which triggered the modal to open */ - public trigger: HTMLElement | null - ) { - super('open'); - } -} - -/** - * A **modal** displays important information to a user without requiring them to navigate - * to a new page. - * @summary Displays information or helps a user focus on a task - * @alias Modal - * @fires {ModalOpenEvent} open - Fires when a user clicks on the trigger or manually opens a modal. - * @fires {ModalCloseEvent} close - Fires when either a user clicks on either the close button or the overlay or manually closes a modal. - */ -@customElement('pf-modal') -export class PfModal extends LitElement { - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - static readonly styles: CSSStyleSheet[] = [style]; - - /** Should the dialog close when user clicks outside the dialog? */ - protected static closeOnOutsideClick = false; - - /** - * The `variant` controls the width of the modal. - * There are three options: `small`, `medium` and `large`. The default is `large`. - */ - @property({ reflect: true }) variant?: 'small' | 'medium' | 'large'; - - /** - * `position="top"` aligns the dialog with the top of the page - */ - @property({ reflect: true }) position?: 'top'; - - @property({ type: Boolean, reflect: true }) open = false; - - /** Optional ID of the trigger element */ - @property() trigger?: string; - - /** @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/returnValue */ - public returnValue = ''; - - @query('#overlay') private overlay?: HTMLElement | null; - @query('#dialog') private dialog?: HTMLElement | null; - @query('#close-button') private closeButton?: HTMLElement | null; - - #headerId = getRandomId(); - #triggerElement: HTMLElement | null = null; - #header: HTMLElement | null = null; - #body: Element[] = []; - #headings: Element[] = []; - #cancelling = false; - - #slots = new SlotController(this, null, 'header', 'description', 'footer'); - - connectedCallback(): void { - super.connectedCallback(); - this.addEventListener('keydown', this.onKeydown); - this.addEventListener('click', this.onClick); - } - - render(): TemplateResult<1> { - const headerId = (this.#header || this.#headings.length) ? this.#headerId : undefined; - const headerLabel = this.#triggerElement ? this.#triggerElement.innerText : undefined; - const hasHeader = this.#slots.hasSlotted('header'); - const hasDescription = this.#slots.hasSlotted('description'); - const hasFooter = this.#slots.hasSlotted('footer'); - - return html` -
- -
- - -
- `; - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - - this.removeEventListener('keydown', this.onKeydown); - - this.#triggerElement?.removeEventListener('click', this.onTriggerClick); - } - - @initializer() - protected async _init(): Promise { - await this.updateComplete; - this.#header = this.querySelector(`[slot$="header"]`); - this.#body = [...this.querySelectorAll(`*:not([slot])`)]; - this.#headings = this.#body.filter(el => el.tagName.slice(0, 1) === 'H'); - - if (this.#triggerElement) { - this.#triggerElement.addEventListener('click', this.onTriggerClick); - this.removeAttribute('hidden'); - } - - if (this.#header) { - this.#header.id = this.#headerId; - } else if (this.#headings.length > 0) { - // Get the first heading in the modal if it exists - this.#headings[0].id = this.#headerId; - } - } - - @observes('open') - protected async openChanged(oldValue?: boolean, newValue?: boolean): Promise { - // loosening types to prevent running these effects in unexpected circumstances - // eslint-disable-next-line eqeqeq - if (oldValue == null || newValue == null || oldValue == newValue) { - return; - } else if (this.open) { - // This prevents background scroll - document.body.style.overflow = 'hidden'; - await this.updateComplete; - // Set the focus to the container - this.dialog?.focus(); - this.dispatchEvent(new ModalOpenEvent(this.#triggerElement)); - } else { - // Return scrollability - document.body.style.overflow = 'auto'; - - await this.updateComplete; - - if (this.#triggerElement) { - this.#triggerElement.focus(); - } - - this.dispatchEvent(this.#cancelling ? new ModalCancelEvent() : new ModalCloseEvent()); - } - } - - @observes('trigger') - protected triggerChanged(): void { - if (this.trigger) { - this.#triggerElement = (this.getRootNode() as Document | ShadowRoot) - .getElementById(this.trigger); - this.#triggerElement?.addEventListener('click', this.onTriggerClick); - } - } - - @bound private onTriggerClick(event: MouseEvent) { - event.preventDefault(); - // TODO: in non-modal case, toggle the dialog - this.showModal(); - } - - @bound private onClick(event: MouseEvent) { - const { open, overlay, dialog } = this; - if (open) { - const path = event.composedPath(); - const { closeOnOutsideClick } = this.constructor as typeof PfModal; - - if (closeOnOutsideClick && path.includes(overlay!) && !path.includes(dialog!)) { - event.preventDefault(); - this.cancel(); - } - } - } - - @bound private onKeydown(event: KeyboardEvent) { - switch (event.key) { - case 'Tab': - if (event.target === this.closeButton) { - event.preventDefault(); - this.dialog?.focus(); - } - return; - case 'Escape': - case 'Esc': - event.preventDefault(); - this.cancel(); - return; - case 'Enter': - if (event.target === this.#triggerElement) { - event.preventDefault(); - this.showModal(); - } - return; - } - } - - private async cancel() { - this.#cancelling = true; - this.open = false; - await this.updateComplete; - this.#cancelling = false; - } - - setTrigger(element: HTMLElement): void { - this.#triggerElement = element; - this.#triggerElement.addEventListener('click', this.onTriggerClick); - } - - /** - * Manually toggles the modal. - * ```js - * modal.toggle(); - * ``` - */ - @bound toggle(): void { - this.open = !this.open; - } - - /** - * Manually opens the modal. - * ```js - * modal.open(); - * ``` - */ - @bound show(): void { - this.open = true; - } - - @bound showModal(): void { - // TODO: non-modal mode - this.show(); - } - - /** - * Manually closes the modal. - * ```js - * modal.close(); - * ``` - * @param returnValue dialog return value - */ - @bound close(returnValue?: string): void { - if (typeof returnValue === 'string') { - this.returnValue = returnValue; - } - - this.open = false; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-modal': PfModal; - } -} diff --git a/elements/pf-panel/README.md b/elements/pf-panel/README.md deleted file mode 100644 index f2eeb4dd94..0000000000 --- a/elements/pf-panel/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Panel -Panel contains content with optional header and footer content. - -## Usage - -```html - -

Panel content

-
-``` diff --git a/elements/pf-panel/demo/bordered.html b/elements/pf-panel/demo/bordered.html deleted file mode 100644 index 5dc10860a0..0000000000 --- a/elements/pf-panel/demo/bordered.html +++ /dev/null @@ -1,15 +0,0 @@ -
- -

Main content

-
-
- - - - diff --git a/elements/pf-panel/demo/footer.html b/elements/pf-panel/demo/footer.html deleted file mode 100644 index 94af2061e3..0000000000 --- a/elements/pf-panel/demo/footer.html +++ /dev/null @@ -1,16 +0,0 @@ -
- -

Main content

-

Footer content

-
-
- - - - diff --git a/elements/pf-panel/demo/header-and-footer.html b/elements/pf-panel/demo/header-and-footer.html deleted file mode 100644 index c46e267126..0000000000 --- a/elements/pf-panel/demo/header-and-footer.html +++ /dev/null @@ -1,17 +0,0 @@ -
- -

Header content

-

Main content

-

Footer content

-
-
- - - - diff --git a/elements/pf-panel/demo/header.html b/elements/pf-panel/demo/header.html deleted file mode 100644 index ad13b9b5bb..0000000000 --- a/elements/pf-panel/demo/header.html +++ /dev/null @@ -1,16 +0,0 @@ -
- -

Header content

-

Main content

-
-
- - - - diff --git a/elements/pf-panel/demo/index.html b/elements/pf-panel/demo/index.html deleted file mode 100644 index 787dbf9bcb..0000000000 --- a/elements/pf-panel/demo/index.html +++ /dev/null @@ -1,15 +0,0 @@ -
- -

Main content

-
-
- - - - diff --git a/elements/pf-panel/demo/raised.html b/elements/pf-panel/demo/raised.html deleted file mode 100644 index e69b7fc0f4..0000000000 --- a/elements/pf-panel/demo/raised.html +++ /dev/null @@ -1,15 +0,0 @@ -
- -

Main content

-
-
- - - - diff --git a/elements/pf-panel/docs/pf-panel.md b/elements/pf-panel/docs/pf-panel.md deleted file mode 100644 index 261f4d037a..0000000000 --- a/elements/pf-panel/docs/pf-panel.md +++ /dev/null @@ -1,135 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - -

Main content

-
-{% endrenderOverview %} - -{% band header="Usage" %} - ### Header - {% htmlexample %} - -

Header content

-

Main content

-
- {% endhtmlexample %} - - ### No Header - {% htmlexample %} - -

Main content

-

Footer content

-
- {% endhtmlexample %} - - ### Header and footer - {% htmlexample %} - -

Header content

-

Main content

-

Footer content

-
- {% endhtmlexample %} - - ### Raised - {% htmlexample %} - -

Main content

-
- {% endhtmlexample %} - - ### Bordered - {% htmlexample %} - -

Main content

-
- {% endhtmlexample %} - - ### Scrollable - {% htmlexample %} - -

- A couple of years ago the Computer Science Club at Bishop's University - ran into a problem. Our student run computer lab was running - unlicensed copies of a propriety operating system. The computers also - had many unlicensed programs installed. It was a big mess. At that - time we had to make an ethical decision. We had to decide whether we - wanted to continue breaking the law or not. We decided against running - software for which we didn't have licenses as it could lead to the lab - being closed. -

-

- A couple of years ago the Computer Science Club at Bishop's University - ran into a problem. Our student run computer lab was running - unlicensed copies of a propriety operating system. The computers also - had many unlicensed programs installed. It was a big mess. At that - time we had to make an ethical decision. We had to decide whether we - wanted to continue breaking the law or not. We decided against running - software for which we didn't have licenses as it could lead to the lab - being closed. -

-

- A couple of years ago the Computer Science Club at Bishop's University - ran into a problem. Our student run computer lab was running - unlicensed copies of a propriety operating system. The computers also - had many unlicensed programs installed. It was a big mess. At that - time we had to make an ethical decision. We had to decide whether we - wanted to continue breaking the law or not. We decided against running - software for which we didn't have licenses as it could lead to the lab - being closed. -

-

Footer content

-
- {% endhtmlexample %} - - ### Scrollable with header and footer - {% htmlexample %} - -

Header content

-

- A couple of years ago the Computer Science Club at Bishop's University - ran into a problem. Our student run computer lab was running - unlicensed copies of a propriety operating system. The computers also - had many unlicensed programs installed. It was a big mess. At that - time we had to make an ethical decision. We had to decide whether we - wanted to continue breaking the law or not. We decided against running - software for which we didn't have licenses as it could lead to the lab - being closed. -

-

- A couple of years ago the Computer Science Club at Bishop's University - ran into a problem. Our student run computer lab was running - unlicensed copies of a propriety operating system. The computers also - had many unlicensed programs installed. It was a big mess. At that - time we had to make an ethical decision. We had to decide whether we - wanted to continue breaking the law or not. We decided against running - software for which we didn't have licenses as it could lead to the lab - being closed. -

-

- A couple of years ago the Computer Science Club at Bishop's University - ran into a problem. Our student run computer lab was running - unlicensed copies of a propriety operating system. The computers also - had many unlicensed programs installed. It was a big mess. At that - time we had to make an ethical decision. We had to decide whether we - wanted to continue breaking the law or not. We decided against running - software for which we didn't have licenses as it could lead to the lab - being closed. -

-

Footer content

-
- {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-panel/pf-panel.css b/elements/pf-panel/pf-panel.css deleted file mode 100644 index 6b492d9b49..0000000000 --- a/elements/pf-panel/pf-panel.css +++ /dev/null @@ -1,199 +0,0 @@ -:host { - /* NB: upstream sets these on the container, which is why we do not use fallbacks here. */ - /** Width of panel */ - --pf-c-panel--Width: auto; - /** Minimum width of panel */ - --pf-c-panel--MinWidth: auto; - /** Maximum width of panel */ - --pf-c-panel--MaxWidth: none; - /** Z-index of panel */ - --pf-c-panel--ZIndex: auto; - /** Background color of panel */ - --pf-c-panel--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - /** Box shadow of panel */ - --pf-c-panel--BoxShadow: none; - /** Border width of panel before element */ - --pf-c-panel--before--BorderWidth: 0; - /** Border color of panel before element */ - --pf-c-panel--before--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** Border width of bordered panel before element */ - --pf-c-panel--m-bordered--before--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); - /** Box shadow of raised panel */ - --pf-c-panel--m-raised--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - /** Z-index of raised panel */ - --pf-c-panel--m-raised--ZIndex: var(--pf-global--ZIndex--sm, 200); - /** Top padding of panel header */ - --pf-c-panel__header--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** Right padding of panel header */ - --pf-c-panel__header--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** Bottom padding of panel header */ - --pf-c-panel__header--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** Left padding of panel header */ - --pf-c-panel__header--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** Maximum height of panel main */ - --pf-c-panel__main--MaxHeight: none; - /** Overflow of panel main */ - --pf-c-panel__main--Overflow: visible; - /** Top padding of panel main body */ - --pf-c-panel__main-body--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** Right padding of panel main body */ - --pf-c-panel__main-body--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** Bottom padding of panel main body */ - --pf-c-panel__main-body--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** Left padding of panel main body */ - --pf-c-panel__main-body--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** Top padding of panel footer */ - --pf-c-panel__footer--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** Right padding of panel footer */ - --pf-c-panel__footer--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** Bottom padding of panel footer */ - --pf-c-panel__footer--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** Left padding of panel footer */ - --pf-c-panel__footer--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** Box shadow of panel footer */ - --pf-c-panel__footer--BoxShadow: none; - /** Maximum height of scrollable panel main */ - --pf-c-panel--m-scrollable__main--MaxHeight: 18.75rem; - /** Overflow of scrollable panel main */ - --pf-c-panel--m-scrollable__main--Overflow: auto; - /** Box shadow of scrollable panel footer */ - --pf-c-panel--m-scrollable__footer--BoxShadow: 0 -0.3125rem 0.25rem -0.25rem rgba(3, 3, 3, 0.16); - position: relative; - z-index: var(--pf-c-panel--ZIndex); - width: var(--pf-c-panel--Width); - min-width: var(--pf-c-panel--MinWidth); - max-width: var(--pf-c-panel--MaxWidth); - background-color: var(--pf-c-panel--BackgroundColor); - box-shadow: var(--pf-c-panel--BoxShadow); - display: block; -} - -:host([variant="bordered"])::before { - position: absolute; - inset: 0; - pointer-events: none; - content: ""; - border: - var(--pf-c-panel--m-bordered--before--BorderWidth, var(--pf-global--BorderWidth--sm, 1px)) - solid - var(--pf-c-panel--before--BorderColor, - var(--pf-global--BorderColor--100, #d2d2d2)); -} - -:host([variant="raised"])::before { - position: absolute; - inset: 0; - pointer-events: none; - content: ""; - box-shadow: var(--pf-c-panel--m-raised--BoxShadow, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - z-index: var(--pf-c-panel--m-raised--ZIndex, var(--pf-global--ZIndex--sm, 200)); -} - -:host([variant="raised"]) { - --pf-c-panel--BoxShadow: var(--pf-c-panel--m-raised--BoxShadow, - var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06))); - --pf-c-panel--ZIndex: var(--pf-c-panel--m-raised--ZIndex, - var(--pf-global--ZIndex--sm, 200)); -} - -:host([scrollable]) { - --pf-c-panel__main--MaxHeight: var(--pf-c-panel--m-scrollable__main--MaxHeight); - --pf-c-panel__main--Overflow: var(--pf-c-panel--m-scrollable__main--Overflow); - --pf-c-panel__footer--BoxShadow: var(--pf-c-panel--m-scrollable__footer--BoxShadow); -} - -[hidden] { - display: none !important; -} - -slot { - display: block; -} - -slot:not([name]) { - max-height: var(--pf-c-panel__main--MaxHeight); - overflow: var(--pf-c-panel__main--Overflow); - padding: - var(--pf-c-panel__main-body--PaddingTop, - var(--pf-global--spacer--md, 1rem)) - var(--pf-c-panel__main-body--PaddingRight, - var(--pf-global--spacer--md, 1rem)) - var(--pf-c-panel__main-body--PaddingBottom, - var(--pf-global--spacer--md, 1rem)) - var(--pf-c-panel__main-body--PaddingLeft, - var(--pf-global--spacer--md, 1rem)); -} - -slot[name="header"] { - padding: - var(--pf-c-panel__header--PaddingTop) - var(--pf-c-panel__header--PaddingRight) - var(--pf-c-panel__header--PaddingBottom) - var(--pf-c-panel__header--PaddingLeft); -} - -slot[name="footer"] { - padding: - var(--pf-c-panel__footer--PaddingTop) - var(--pf-c-panel__footer--PaddingRight) - var(--pf-c-panel__footer--PaddingBottom) - var(--pf-c-panel__footer--PaddingLeft); - box-shadow: var(--pf-c-panel__footer--BoxShadow); -} - -hr { - --pf-c-divider--BorderWidth--base: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-divider--BorderColor--base: var(--pf-c-divider--BackgroundColor); - --pf-c-divider--Height: var(--pf-c-divider--BorderWidth--base); - --pf-c-divider--BackgroundColor: var(--pf-global--BorderColor--100, #d2d2d2); - --pf-c-divider--after--BackgroundColor: var(--pf-c-divider--BorderColor--base); - --pf-c-divider--after--FlexBasis: 100%; - --pf-c-divider--after--Inset: 0%; - --pf-c-divider--m-vertical--after--FlexBasis: 100%; - --pf-c-divider--m-horizontal--Display: flex; - --pf-c-divider--m-horizontal--FlexDirection: row; - --pf-c-divider--m-horizontal--after--Height: var(--pf-c-divider--Height); - --pf-c-divider--m-horizontal--after--Width: auto; - --pf-c-divider--m-vertical--Display: inline-flex; - --pf-c-divider--m-vertical--FlexDirection: column; - --pf-c-divider--m-vertical--after--Height: auto; - --pf-c-divider--m-vertical--after--Width: var(--pf-c-divider--BorderWidth--base); - --pf-hidden-visible--visible--Display: var(--pf-c-divider--Display); - --pf-c-divider--Display: var(--pf-c-divider--m-horizontal--Display); - --pf-c-divider--FlexDirection: var(--pf-c-divider--m-horizontal--FlexDirection); - --pf-c-divider--after--Width: var(--pf-c-divider--m-horizontal--after--Width); - --pf-c-divider--after--Height: var(--pf-c-divider--m-horizontal--after--Height); - width: 100%; - height: auto; - display: var(--pf-c-divider--Display); - flex-direction: var(--pf-c-divider--FlexDirection); - align-items: center; - align-self: stretch; - flex-shrink: 0; - justify-content: center; - border: 0; -} - -hr::after { - align-self: stretch; - width: var(--pf-c-divider--after--Width); - height: var(--pf-c-divider--after--Height); - content: ""; - background-color: var(--pf-c-divider--after--BackgroundColor); - justify-self: center; - padding: 0; - margin: 0; - flex-basis: calc(var(--pf-c-divider--after--FlexBasis) - var(--pf-c-divider--after--Inset) * 2); -} - -::slotted(:is(p, h1, h2, h3, h4, h5, h6):first-of-type) { - margin-block-start: 0; -} - -::slotted(:is(p, h1, h2, h3, h4, h5, h6):last-of-type) { - margin-block-end: 0; -} - -::slotted(:is(p, h1, h2, h3, h4, h5, h6):is(:last-of-type, :first-of-type)) { - margin-block: 0; -} diff --git a/elements/pf-panel/pf-panel.ts b/elements/pf-panel/pf-panel.ts deleted file mode 100644 index d645969774..0000000000 --- a/elements/pf-panel/pf-panel.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; - -import styles from './pf-panel.css'; - -/** - * The **panel** component is a container that supports flexible content layouts. It can - * be used to house other components such as fields, forms, videos, buttons, and more. - * The panel should not be confused with the [drawer](https://www.patternfly.org/v4/components/drawer/design-guidelines/) - * component, which allows you to surface information via a collapsable container. - * @alias Panel - */ -@customElement('pf-panel') -export class PfPanel extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - @property({ type: Boolean, reflect: true }) scrollable = false; - - @property({ reflect: true }) variant?: 'raised' | 'bordered'; - - #slots = new SlotController(this, 'header', null, 'footer'); - - render(): TemplateResult<1> { - const hasHeader = this.#slots.hasSlotted('header'); - const hasFooter = this.#slots.hasSlotted('footer'); - return html` -
- - -
-
- - -
- - -
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-panel': PfPanel; - } -} diff --git a/elements/pf-panel/test/pf-panel.spec.ts b/elements/pf-panel/test/pf-panel.spec.ts deleted file mode 100644 index ea57ea708d..0000000000 --- a/elements/pf-panel/test/pf-panel.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfPanel } from '@patternfly/elements/pf-panel/pf-panel.js'; - -describe('', function() { - describe('simply instantiating', function() { - let element: PfPanel; - beforeEach(async function() { - element = await createFixture (html``); - }); - - it('imperatively instantiates', function() { - expect(document.createElement('pf-panel')).to.be.an.instanceof(PfPanel); - }); - - it('should upgrade', function() { - const klass = customElements.get('pf-panel'); - expect(element) - .to.be.an.instanceOf(klass) - .and - .to.be.an.instanceOf(PfPanel); - }); - }); -}); diff --git a/elements/pf-popover/README.md b/elements/pf-popover/README.md deleted file mode 100644 index d6c88c3201..0000000000 --- a/elements/pf-popover/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Popover - -A Popover displays content in a non-modal dialog and adds contextual information or provides resources via text and links. - -## Usage - -Read more about popovers in the [PatternFly Elements Popover documentation](https://patternflyelements.org/components/popover) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-popover/pf-popover.js'; -``` - -## Usage - -### Basic Popover - -```html - - Toggle popover - -``` - -```html - -

Popover heading

-
Popovers are triggered by click rather than hover.
- Popover footer - Toggle popover -
-``` diff --git a/elements/pf-popover/demo/content.html b/elements/pf-popover/demo/content.html deleted file mode 100644 index 4ba3e0be62..0000000000 --- a/elements/pf-popover/demo/content.html +++ /dev/null @@ -1,65 +0,0 @@ -
-
- Content attributes - - Toggle popover - -
- -
- Content slots - -

Popover heading

-
- Popovers are triggered by click rather than hover. -
- Popover footer - Toggle popover -
-
- -
- Custom Heading Level - - Toggle popover - -
-
- - - - diff --git a/elements/pf-popover/demo/edge-of-viewport.html b/elements/pf-popover/demo/edge-of-viewport.html deleted file mode 100644 index 62ed4bad7b..0000000000 --- a/elements/pf-popover/demo/edge-of-viewport.html +++ /dev/null @@ -1,29 +0,0 @@ -

- When the viewport is 1190 pixels wide, (such that the "toggle popover" button appears - at the far edge of the viewport, the popover at the far end of the viewport - must not cause a horizontal scrollbar which would otherwise not have been there. -

-Spacer -Spacer -Spacer -Spacer -Spacer -Spacer -Spacer -Spacer -Spacer -Spacer -Spacer -Spacer - -

In this popover, the content is quite wide, - so as to force a horizontal scrollbar if the - dialog content is improperly rendered.

- Toggle popover -
- - - diff --git a/elements/pf-popover/demo/flip.html b/elements/pf-popover/demo/flip.html deleted file mode 100644 index 11e9a0d1ce..0000000000 --- a/elements/pf-popover/demo/flip.html +++ /dev/null @@ -1,117 +0,0 @@ -
-
- No flip - - Toggle popover - -
- -
- Flip fallback - - - Toggle popover - -
-
- - - - diff --git a/elements/pf-popover/demo/icons.html b/elements/pf-popover/demo/icons.html deleted file mode 100644 index f29104ed8f..0000000000 --- a/elements/pf-popover/demo/icons.html +++ /dev/null @@ -1,53 +0,0 @@ -
-
- Icon attribute - - Toggle popover - -
- -
- Icon slot - - -

Popover with icon

-
Popovers are triggered by click rather than hover.
-
Popover footer
- Toggle popover -
-
- -
- Custom Icon Set - - -
Popover heading
-
- Popovers are triggered by click rather than hover. -
- Popover footer - Toggle popover -
-
-
- - - - diff --git a/elements/pf-popover/demo/index.html b/elements/pf-popover/demo/index.html deleted file mode 100644 index 3a2dea0bc7..0000000000 --- a/elements/pf-popover/demo/index.html +++ /dev/null @@ -1,91 +0,0 @@ -
-
- Triggered by reference - - - Toggle popover -
- -
- No hide on outside click - - Toggle popover - -
- -
- Close popover from content - -

Popover heading

-
Popovers are triggered by click rather than hover.
- - Close popover - - Toggle popover -
-
- -
- Without header, footer, or close - -
- This popover has no padding and is intended for use with content that has its own spacing and should touch the - edges of the popover container. -
- Toggle popover -
-
- -
- Width auto - -
Removes fixed-width and allows width to be defined by contents.
- Toggle popover -
-
-
- - - - diff --git a/elements/pf-popover/docs/pf-popover.md b/elements/pf-popover/docs/pf-popover.md deleted file mode 100644 index 4353658bc7..0000000000 --- a/elements/pf-popover/docs/pf-popover.md +++ /dev/null @@ -1,136 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - - - Toggle popover - - -{% endrenderOverview %} - -{% band header="Usage" %} - {% htmlexample %} - - Toggle popover - - {% endhtmlexample %} - - **Note**: Unlike the [Patternfly React implementation][withfocustrap], this - component does not trap focus in the popover dialog. If you would like to trap - focus, consider using a modal dialog instead. - - ### Activating programmatically - - Use the `show()` method to activate the popover. - - - HTML - - -```html - - -Hover to cite - - - Richard M. Stallman - Free software is a political movement; open source is a development model. - -``` - - - Lit - - -```js -import { LitElement, html } from 'lit'; -import '@patternfly/elements/pf-button/pf-button.js'; -import '@patternfly/elements/pf-popover/pf-popover.js'; - -class Citer extends LitElement { - render() { - return html` - Hover to Cite - - - Richard M. Stallman - Free software is a political movement; open source is a development model. - - `; - } - - get #popover() { return this.shadowRoot.querySelector('pf-popover'); } - - #onMouseover() { this.#popover.show(); } - - #onMouseout() { this.#popover.hide(); } -} -``` - - - React - - -```jsx -import { Button } from '@patternfly/elements/react/pf-button/pf-button.js'; -import { Popover } from '@patternfly/elements/react/pf-popover/pf-popover.js'; -import { useRef } from 'react'; - - -export function Citer() { - const popoverRef = useRef(null); - - const onMouseover = e => void popoverRef.current.show(); - - const onMouseout = e => void popoverRef.current.hide(); - - return ( - <> - - - Richard M. Stallman - Free software is a political movement; open source is a development model. - - - ); -} -``` - - - -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} - -[withfocustrap]: https://www.patternfly.org/v4/components/popover#:~:textwithfocustrap diff --git a/elements/pf-popover/pf-popover.css b/elements/pf-popover/pf-popover.css deleted file mode 100644 index d471796398..0000000000 --- a/elements/pf-popover/pf-popover.css +++ /dev/null @@ -1,224 +0,0 @@ -:host { - display: inline; - /** Width of the arrow */ - --_floating-arrow-size: var(--pf-c-popover__arrow--Width, var(--pf-global--arrow--width-lg, 1.5625rem)); - /** Heading font color */ - --_header-text-color: var(--pf-c-popover__title-text--Color, inherit); - /** Heading icon font color */ - --_header-icon-color: var(--pf-c-popover__title-icon--Color, var(--pf-global--Color--100, #151515)); - --_animation-speed: var(--pf-popover--animation-speed, 300ms); - --_z-index: var(--pf-popover--z-index, 9999); -} - -.visually-hidden { - position: fixed; - top: 0; - left: 0; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -[hidden] { - display: none !important; -} - -#container { - display: inline-flex; - position: relative; -} - -#trigger { - display: inline-block; - position: relative; -} - -#arrow { - display: block; - position: absolute; - /** Arrow background color */ - background-color: var(--pf-c-popover__arrow--BackgroundColor, var(--pf-global--BackgroundColor--100, #fff)); - /** Arrow box shadow */ - box-shadow: var( - --pf-c-popover__arrow--BoxShadow, - var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)) - ); - content: ''; - /** Height of the arrow */ - height: var(--pf-c-popover__arrow--Height, var(--pf-global--arrow--width-lg, 1.5625rem)); - /** Width of the arrow */ - width: var(--pf-c-popover__arrow--Width, var(--pf-global--arrow--width-lg, 1.5625rem)); - rotate: 45deg; - z-index: -1; - pointer-events: none; -} - -#popover { - display: block; - position: absolute; - opacity: 0; - z-index: -1; - transition: visibility 0s, opacity var(--_animation-speed) cubic-bezier(0.54, 1.5, 0.38, 1.11) 0s; - left: 0; - top: 0; - translate: var(--_floating-content-translate); - /** Popover box shadow */ - box-shadow: var( - --pf-c-popover--BoxShadow, - var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)) - ); - border: 0; - padding: 0; - visibility: hidden; -} - -#popover[open] { - opacity: 1; - z-index: var(--_z-index); - visibility: visible; -} - -[part='content'] { - position: relative; - /** Popover padding (top, right, bottom, left) */ - padding: var(--pf-c-popover__content--PaddingTop, var(--pf-global--spacer--md, 1rem)) - var(--pf-c-popover__content--PaddingRight, var(--pf-global--spacer--md, 1rem)) - var(--pf-c-popover__content--PaddingBottom, var(--pf-global--spacer--md, 1rem)) - var(--pf-c-popover__content--PaddingLeft, var(--pf-global--spacer--md, 1rem)); - word-break: break-word; - /** Popover line height */ - line-height: var(--pf-c-popover--line-height, 1.5); - /** Popover font-size */ - font-size: var(--pf-c-popover__content--FontSize, var(--pf-global--FontSize--sm, 0.875rem)); - color: var(--pf-c-popover__content--Color, var(--pf-global--Color--100, #151515)); - /** Popover background color */ - background-color: var(--pf-c-popover__content--BackgroundColor, var(--pf-global--BackgroundColor--100, #fff)); - /** Popover max-width */ - max-width: var( - --pf-c-popover--MaxWidth, - calc(var(--pf-c-popover__content--PaddingLeft, 1rem) + var(--pf-c-popover__content--PaddingRight, 1rem) + 18.75rem) - ); - /** Popover min-width */ - min-width: var( - --pf-c-popover--MinWidth, - calc(var(--pf-c-popover__content--PaddingLeft, 1rem) + var(--pf-c-popover__content--PaddingRight, 1rem) + 18.75rem) - ); - width: max-content; -} - -[part='close-button'] { - cursor: pointer; - position: absolute; - /** Close button right position */ - right: var( - --pf-c-popover--c-button--Right, - calc(var(--pf-c-popover__content--PaddingRight, 1rem) - var(--pf-global--spacer--md, 1rem)) - ); - /** Close button top position */ - top: var( - --pf-c-popover--c-button--Top, - calc(var(--pf-c-popover__content--PaddingTop, 1rem) - var(--pf-global--spacer--form-element, 0.375rem)) - ); -} - -[part='content'] > [part='close-button']:not([hidden]) ~ *:not([hidden]) { - /** Padding between close button and its immediate sibling */ - padding-right: var(--pf-c-popover--c-button--sibling--PaddingRight, var(--pf-global--spacer--2xl, 3rem)); -} - -[part='header'] { - display: flex; - align-items: baseline; -} - -[part='icon'] { - color: var(--_header-icon-color); - /** Heading icon right margin */ - margin-right: var(--pf-c-popover__title-icon--MarginRight, var(--pf-global--spacer--sm, 0.5rem)); -} - -[part='icon'] ::slotted(*), -[part='icon'] * { - vertical-align: -0.125em; -} - -[part='icon'], -[part='heading']::slotted(:is(h2, h3, h4, h5, h6)), -[part='heading'] :is(h2, h3, h4, h5, h6) { - /** Header font-size */ - font-size: var(--pf-c-popover__title--FontSize, var(--pf-global--FontSize--md, 1rem)); - font-weight: var(--pf-global--FontWeight--normal, 400); - --pf-icon--size: var( - --pf-c-popover__title--FontSize, - var(--pf-global--FontSize--md, var(--pf-global--icon--FontSize--md, 1em)) - ); -} - -[part='heading']::slotted(:is(h2, h3, h4, h5, h6)), -[part='heading'] :is(h2, h3, h4, h5, h6) { - color: var(--_header-text-color); - margin-top: 0; - /** Header bottom margin */ - margin-bottom: var(--pf-c-popover__title--MarginBottom, var(--pf-global--spacer--sm, 0.5rem)); - /** Header line height */ - line-height: var(--pf-c-popover__title--LineHeight, var(--pf-global--LineHeight--md, 1.5)); - /** Header font-family */ - font-family: var( - --pf-c-popover__title--FontFamily, - var( - --pf-global--FontFamily--heading--sans-serif, - 'RedHatDisplay', - 'Overpass', - overpass, - helvetica, - arial, - sans-serif - ) - ); -} - -[part='body'] { - display: block; - word-wrap: break-word; -} - -[part='footer'] { - /** Footer top margin */ - margin-top: var(--pf-c-popover__footer--MarginTop, var(--pf-global--spacer--md, 1rem)); -} - -:host([alert-severity='default']) { - /** Default alert heading color */ - --_header-text-color: var(--pf-c-popover--m-default__title-text--Color, var(--pf-global--default-color--300, #003737)); - /** Default alert icon color */ - --_header-icon-color: var(--pf-c-popover--m-default__title-icon--Color, var(--pf-global--default-color--200, #009596)); -} - -:host([alert-severity='info']) { - /** Info alert heading color */ - --_header-text-color: var(--pf-c-popover--m-info__title-text--Color, var(--pf-global--info-color--200, #002952)); - /** Info alert icon color */ - --_header-icon-color: var(--pf-c-popover--m-info__title-icon--Color, var(--pf-global--info-color--100, #2b9af3)); -} - -:host([alert-severity='warning']) { - /** Warning alert icon color */ - --_header-icon-color: var(--pf-c-popover--m-warning__title-icon--Color, var(--pf-global--warning-color--100, #f0ab00)); - /** Warning alert heading color */ - --_header-text-color: var(--pf-c-popover--m-warning__title-text--Color, var(--pf-global--warning-color--200, #795600)); -} - -:host([alert-severity='success']) { - /** Success alert icon color */ - --_header-icon-color: var(--pf-c-popover--m-success__title-icon--Color, var(--pf-global--success-color--100, #3e8635)); - /** Success alert heading color */ - --_header-text-color: var(--pf-c-popover--m-success__title-text--Color, var(--pf-global--success-color--200, #1e4f18)); -} - -:host([alert-severity='danger']) { - /** Danger alert icon color */ - --_header-icon-color: var(--pf-c-popover--m-danger__title-icon--Color, var(--pf-global--danger-color--100, #c9190b)); - /** Danger alert heading color */ - --_header-text-color: var(--pf-c-popover--m-danger__title-text--Color, var(--pf-global--danger-color--200, #a30000)); -} diff --git a/elements/pf-popover/pf-popover.ts b/elements/pf-popover/pf-popover.ts deleted file mode 100644 index 6aafde3dde..0000000000 --- a/elements/pf-popover/pf-popover.ts +++ /dev/null @@ -1,422 +0,0 @@ -import type { Placement } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; - -import { LitElement, nothing, html, type PropertyValues, isServer, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { query } from 'lit/decorators/query.js'; -import { styleMap } from 'lit/directives/style-map.js'; -import { classMap } from 'lit/directives/class-map.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; -import { deprecation } from '@patternfly/pfe-core/decorators/deprecation.js'; -import { bound } from '@patternfly/pfe-core/decorators/bound.js'; -import { ComposedEvent, StringListConverter } from '@patternfly/pfe-core/core.js'; - -import '@patternfly/elements/pf-button/pf-button.js'; - -import styles from './pf-popover.css'; - -type HeadingLevel = 2 | 3 | 4 | 5 | 6; - -type AlertSeverity = 'default' | 'info' | 'warning' | 'success' | 'danger'; - -export class PopoverHideEvent extends ComposedEvent { - constructor() { - super('hide'); - } -} - -export class PopoverHiddenEvent extends ComposedEvent { - constructor() { - super('hidden'); - } -} - -export class PopoverShowEvent extends ComposedEvent { - constructor() { - super('show'); - } -} - -export class PopoverShownEvent extends ComposedEvent { - constructor() { - super('shown'); - } -} - -/** - * A **Popover** displays content in a non-modal dialog and adds contextual information or provides resources via text and links. - * @summary Toggle the visibility of helpful or contextual information. - * @alias Popover - */ -@customElement('pf-popover') -export class PfPopover extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - private static instances = new Set(); - - private static alertIcons = new Map(Object.entries({ - default: 'bell', - info: 'circle-info', - success: 'circle-check', - warning: 'triangle-exclamation', - danger: 'circle-exclamation', - } satisfies Record) as [AlertSeverity, string][]); - - static { - if (!isServer) { - document.addEventListener('click', function(event) { - for (const instance of PfPopover.instances) { - if (!instance.noOutsideClick) { - instance.#outsideClick(event); - } - } - }); - } - } - - /** - * Indicates the initial popover position. - * There are 12 options: `top`, `bottom`, `left`, `right`, `top-start`, `top-end`, - * `bottom-start`, `bottom-end`, `left-start`, `left-end`,`right-start`, `right-end`. - * The default is `top`. - */ - @property({ reflect: true }) position: Placement = 'top'; - - /** - * The content rendered in the popover's heading. - */ - @property({ reflect: true }) heading?: string; - - /** - * The content rendered in the popover's body. - */ - @property({ reflect: true }) body?: string; - - /** - * The content rendered in the popover's footer. - */ - @property({ reflect: true }) footer?: string; - - /** - * The icon placed before the popover's heading. - */ - @property({ reflect: true }) icon?: string; - - /** - * The accessible label for the popover. This is required if the no heading is set. - */ - @property({ reflect: true }) label?: string; - - /** - * The distance to set between the popover and its trigger element. - */ - @property({ type: Number, reflect: true }) distance?: number; - - /** - * The flip order when flip is enabled and the initial position is not possible. - * There are 12 options: `top`, `bottom`, `left`, `right`, `top-start`, `top-end`, - * `bottom-start`, `bottom-end`, `left-start`, `left-end`,`right-start`, `right-end`. - * The default is [oppositePlacement], where only the opposite placement is tried. - */ - @property({ - attribute: 'flip-behavior', - converter: StringListConverter, - }) flipBehavior?: Placement[]; - - /** - * Disable the flip behavior. The default is `false`. - */ - @property({ type: Boolean, reflect: true, attribute: 'no-flip' }) noFlip = false; - - /** - * The heading level to use for the popover's header. The default is `h6`. - */ - @property({ - type: Number, - reflect: true, - attribute: 'heading-level', - }) headingLevel?: HeadingLevel; - - /** - * Indicates which icon set to use for the header's icon. - * The default is `fas` (Font Awesome Free Solid). - */ - @property({ reflect: true, attribute: 'icon-set' }) iconSet?: string; - - /** - * Hide the close button. The default is `false`. - */ - @property({ type: Boolean, reflect: true, attribute: 'hide-close' }) hideClose?: boolean; - - /** - * Indicates the severity variant to use for the alert popover. - * There are five options: `default`, `info`, `warning`, `success`, and `danger`. - */ - @property({ reflect: true, attribute: 'alert-severity' }) alertSeverity?: AlertSeverity; - - /** - * The accessible label for the popover's close button. The default is `Close popover`. - */ - @property({ reflect: true, attribute: 'accessible-close-label' }) accessibleCloseLabel?: string; - - /** - * @deprecated do not use the color-palette attribute, which was added by mistake. use context-providing containers (e.g. rh-card) instead - */ - @deprecation({ - alias: 'accessible-close-label', - attribute: 'close-label', - }) closeButtonLabel?: string; - - /** - * The text announced by the screen reader to indicate the popover's severity. - * The default is `${alertSeverity} alert:`. - */ - @property({ reflect: true, attribute: 'alert-severity-text' }) alertSeverityText?: string; - - /** - * Don't hide the popover when clicking ouside of it. - */ - @property({ - type: Boolean, - reflect: true, - attribute: 'no-outside-click', - }) noOutsideClick?: boolean; - - /** - * The ID of the element to attach the popover to. - */ - @property({ reflect: true }) trigger?: string; - - @query('#popover') private _popover!: HTMLDialogElement; - @query('#trigger') private _slottedTrigger?: HTMLElement | null; - @query('#arrow') private _arrow!: HTMLDivElement; - - /** True before the show animation begins and after the hide animation ends */ - #hideDialog = true; - - #referenceTrigger?: HTMLElement | null = null; - - #float = new FloatingDOMController(this, { - content: () => this._popover, - arrow: () => this._arrow, - invoker: () => this.#referenceTrigger || this._slottedTrigger, - }); - - #slots = new SlotController(this, null, 'icon', 'heading', 'body', 'footer'); - - constructor() { - super(); - if (!isServer) { - this.addEventListener('keydown', this.#onKeydown); - } - } - - render(): TemplateResult<1> { - const { alignment, anchor, styles } = this.#float; - const hasFooter = this.#slots.hasSlotted('footer') || !!this.footer; - const hasHeading = this.#slots.hasSlotted('heading') || !!this.heading; - const hasIcon = this.#slots.hasSlotted('icon') || !!this.icon || !!this.alertSeverity; - - // https://github.com/asyncLiz/minify-html-literals/issues/37 - let headingContent = html`
${this.heading ?? ''}
`; - switch (this.headingLevel) { - case 2: headingContent = html`

${this.heading ?? ''}

`; break; - case 3: headingContent = html`

${this.heading ?? ''}

`; break; - case 4: headingContent = html`

${this.heading ?? ''}

`; break; - case 5: headingContent = html`
${this.heading ?? ''}
`; break; - } - - const headingSlotWithFallback = html` - - ${headingContent} - `; - - const headerIcon = this.icon - ?? PfPopover.alertIcons.get(this.alertSeverity as AlertSeverity) - ?? ''; - - return html` - -
- - - -
- -
- - - - - - - ${!(hasHeading && hasIcon) ? headingSlotWithFallback : html` - -
- - - - - - - ${!this.alertSeverity ? nothing : html` - ${this.alertSeverityText ?? `${this.alertSeverity} alert:`}`} - ${headingSlotWithFallback} -
`} - - ${this.body ?? ''} - -
- - ${this.footer} -
-
-
-
- `; - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - PfPopover.instances.delete(this); - this.#referenceTrigger?.removeEventListener('click', this.toggle); - this.#referenceTrigger?.removeEventListener('keydown', this.#onKeydown); - } - - #getReferenceTrigger() { - if (isServer || !this.trigger) { - return null; - } else { - return (this.getRootNode() as Document | ShadowRoot).getElementById(this.trigger); - } - } - - #triggerChanged() { - const oldReferenceTrigger = this.#referenceTrigger; - this.#referenceTrigger = this.#getReferenceTrigger(); - if (oldReferenceTrigger !== this.#referenceTrigger) { - oldReferenceTrigger?.removeEventListener('click', this.toggle); - oldReferenceTrigger?.removeEventListener('keydown', this.#onKeydown); - this.#referenceTrigger?.addEventListener('click', this.toggle); - this.#referenceTrigger?.addEventListener('keydown', this.#onKeydown); - } - } - - #onKeydown = (event: KeyboardEvent) => { - switch (event.key) { - case 'Escape': - case 'Esc': - event.preventDefault(); - this.hide(); - return; - case 'Enter': - if (event.target === this.#referenceTrigger || event.target === this._slottedTrigger) { - event.preventDefault(); - this.show(); - } - return; - } - }; - - #outsideClick(event: MouseEvent) { - if (this.#hideDialog) { - return; - } - const path = event.composedPath(); - if (!path.includes(this) && !path.includes(this.#referenceTrigger as HTMLElement)) { - this.hide(); - } - } - - /** - * Removes event listeners from the old trigger element and attaches - * them to the new trigger element. - * @param changed changed props - */ - override willUpdate(changed: PropertyValues): void { - if (changed.has('trigger')) { - this.#triggerChanged(); - } - } - - /** - * Toggle the popover - */ - @bound async toggle(): Promise { - if (this.#float.open) { - this.hide(); - } else { - this.show(); - } - } - - /** - * Opens the popover - */ - @bound async show(): Promise { - this.#hideDialog = false; - this.requestUpdate(); - this.dispatchEvent(new PopoverShowEvent()); - await this.updateComplete; - await this.#float.show({ - offset: this.distance ?? 25, - placement: this.position, - flip: !this.noFlip, - fallbackPlacements: this.flipBehavior, - }); - this._popover?.show(); - this.dispatchEvent(new PopoverShownEvent()); - PfPopover.instances.add(this); - } - - /** - * Closes the popover - */ - @bound async hide(): Promise { - this.dispatchEvent(new PopoverHideEvent()); - await this.#float.hide(); - this._popover?.close(); - this.dispatchEvent(new PopoverHiddenEvent()); - PfPopover.instances.delete(this); - this.#hideDialog = true; - this.requestUpdate(); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-popover': PfPopover; - } -} diff --git a/elements/pf-progress-stepper/README.md b/elements/pf-progress-stepper/README.md deleted file mode 100644 index 7d7b36e220..0000000000 --- a/elements/pf-progress-stepper/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# PatternFly Elements Progress Stepper - -A component that gives the user a visual representation of the current state of -their progress through an application (typically a multistep form). - -Read more about Progress Stepper in the [PatternFly Elements Progress Steps -documentation][docs]. - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-progress-stepper/pf-progress-stepper.js'; -``` - -## Usage - -```html - - Completed - Issue - Failure - Running - Last - -``` - -[docs]: https://patternflyelements.org/components/progress-stepper diff --git a/elements/pf-progress-stepper/demo/alignment.html b/elements/pf-progress-stepper/demo/alignment.html deleted file mode 100644 index 4c30e01fe4..0000000000 --- a/elements/pf-progress-stepper/demo/alignment.html +++ /dev/null @@ -1,38 +0,0 @@ -
-

With alignment

- - - - First Step - Second Step - Third Step - -
- - - - diff --git a/elements/pf-progress-stepper/demo/compact.html b/elements/pf-progress-stepper/demo/compact.html deleted file mode 100644 index 14777bad9c..0000000000 --- a/elements/pf-progress-stepper/demo/compact.html +++ /dev/null @@ -1,38 +0,0 @@ -
-

Compact

- - - - First Step - Second Step - Third Step - -
- - - - diff --git a/elements/pf-progress-stepper/demo/custom-icons.html b/elements/pf-progress-stepper/demo/custom-icons.html deleted file mode 100644 index 4801eea447..0000000000 --- a/elements/pf-progress-stepper/demo/custom-icons.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - Successful completion - In process - Pending - -
- - - - diff --git a/elements/pf-progress-stepper/demo/danger.html b/elements/pf-progress-stepper/demo/danger.html deleted file mode 100644 index a38a7386e2..0000000000 --- a/elements/pf-progress-stepper/demo/danger.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - First Step - Second Step - Third Step - Fourth Step - Fifth Step - -
- - - - diff --git a/elements/pf-progress-stepper/demo/index.html b/elements/pf-progress-stepper/demo/index.html deleted file mode 100644 index 4dff746a06..0000000000 --- a/elements/pf-progress-stepper/demo/index.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - First Step - Second Step - Third Step - -
- - - - diff --git a/elements/pf-progress-stepper/demo/info.html b/elements/pf-progress-stepper/demo/info.html deleted file mode 100644 index 7ed919373b..0000000000 --- a/elements/pf-progress-stepper/demo/info.html +++ /dev/null @@ -1,37 +0,0 @@ -
- - First Step - Second Step - Third Step - Fourth Step - Fifth Step - -
- - - - diff --git a/elements/pf-progress-stepper/demo/step-descriptions.html b/elements/pf-progress-stepper/demo/step-descriptions.html deleted file mode 100644 index ff626f626d..0000000000 --- a/elements/pf-progress-stepper/demo/step-descriptions.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - First Step - Second Step - Third Step - -
- - - - diff --git a/elements/pf-progress-stepper/docs/pf-progress-stepper.md b/elements/pf-progress-stepper/docs/pf-progress-stepper.md deleted file mode 100644 index d3c055f7df..0000000000 --- a/elements/pf-progress-stepper/docs/pf-progress-stepper.md +++ /dev/null @@ -1,37 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - - Completed - Issue - Failure - Running - Last - -{% endrenderOverview %} - -{% band header="Usage" %} - {% htmlexample %} - - Completed - Issue - Failure - Running - Last - - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderSlots for='pf-progress-step', header='Slots on `pf-progress-step`' %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-progress-stepper/pf-progress-step.css b/elements/pf-progress-stepper/pf-progress-step.css deleted file mode 100644 index 2f1e22fca0..0000000000 --- a/elements/pf-progress-stepper/pf-progress-step.css +++ /dev/null @@ -1,124 +0,0 @@ -[hidden] { - display: none !important; -} - -#icon { - z-index: var(--pf-c-progress-stepper__step-icon--ZIndex); - display: flex; - align-items: center; - justify-content: center; - width: var(--pf-c-progress-stepper__step-icon--Width); - height: var(--pf-c-progress-stepper__step-icon--Height); - color: var(--pf-c-progress-stepper__step-icon--Color); - background-color: var(--pf-c-progress-stepper__step-icon--BackgroundColor); - border: var(--pf-c-progress-stepper__step-icon--BorderWidth) solid var(--pf-c-progress-stepper__step-icon--BorderColor); - border-radius: 50%; - font-size: var(--pf-c-progress-stepper__step-icon--FontSize); - --pf-icon--size: 1.125em; -} - -#main { - position: var(--pf-c-progress-stepper__step-main--Position, initial); - min-width: 0; - margin: var(--pf-c-progress-stepper__step-main--MarginTop) var(--pf-c-progress-stepper__step-main--MarginRight) var(--pf-c-progress-stepper__step-main--MarginBottom) var(--pf-c-progress-stepper__step-main--MarginLeft); - text-align: var(--pf-c-progress-stepper--step-main--TextAlign, auto); - overflow-wrap: anywhere; -} - -:host(:not([current])) #main.compact { - position: fixed; - top: 0; - left: 0; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - margin-bottom: var(--pf-c-progress-stepper--m-compact__step-main--MarginBottom); -} - -:host([current]) #main.compact { - grid-column: 1/-1; - grid-row: 1/2; -} - -#title { - font-size: var(--pf-c-progress-stepper__step-title--FontSize); - font-weight: var(--pf-c-progress-stepper__step-title--FontWeight); - color: var(--pf-c-progress-stepper__step-title--Color); - text-align: var(--pf-c-progress-stepper__step-title--TextAlign); - border: 0; -} - -#description { - margin-top: var(--pf-c-progress-stepper__step-description--MarginTop); - font-size: var(--pf-c-progress-stepper__step-description--FontSize); - color: var(--pf-c-progress-stepper__step-description--Color); - text-align: var(--pf-c-progress-stepper__step-description--TextAlign); - display: block; -} - -:host { - display: contents; -} - -#connector { - position: relative; - display: flex; - justify-content: var(--pf-c-progress-stepper__step-connector--JustifyContent); - width: 100%; -} - -#connector.compact { - min-width: var(--pf-c-progress-stepper--m-compact__step-connector--MinWidth); - grid-row: var(--pf-c-progress-stepper--m-compact__step-connector--GridRow); - padding-bottom: var(--pf-c-progress-stepper--m-compact__step-connector--PaddingBottom); -} - -:host(:not(:last-of-type)) #main::before { - content: var(--pf-c-progress-stepper__step-main--before--Content); - position: absolute; - top: calc(100% + var(--pf-c-progress-stepper__step-main--MarginTop)); - left: calc(50% - var(--pf-c-progress-stepper__step-connector--before--BorderRightWidth) / 2); - width: auto; - height: calc(var(--pf-c-progress-stepper__step-main--MarginTop) + var(--pf-c-progress-stepper__step-main--MarginBottom)); - border-right: var(--pf-c-progress-stepper__step-connector--before--BorderRightWidth) solid var(--pf-c-progress-stepper__step-connector--before--BorderRightColor); -} - -:host(:not(:last-of-type)) #connector::before { - position: absolute; - top: var(--pf-c-progress-stepper__step-connector--before--Top); - left: var(--pf-c-progress-stepper__step-connector--before--Left); - width: var(--pf-c-progress-stepper__step-connector--before--Width); - height: var(--pf-c-progress-stepper__step-connector--before--Height); - content: var(--pf-c-progress-stepper__step-connector--before--Content); - border-right: var(--pf-c-progress-stepper__step-connector--before--BorderRightWidth) solid var(--pf-c-progress-stepper__step-connector--before--BorderRightColor); - border-bottom: var(--pf-c-progress-stepper__step-connector--before--BorderBottomWidth) solid var(--pf-c-progress-stepper__step-connector--before--BorderBottomColor); - transform: var(--pf-c-progress-stepper__step-connector--before--Transform); -} - -:host([current]) { - --pf-c-progress-stepper__step-title--FontWeight: var(--pf-c-progress-stepper__step--m-current__step-title--FontWeight); - --pf-c-progress-stepper__step-title--Color: var(--pf-c-progress-stepper__step--m-current__step-title--Color); -} - -:host([variant="success"]) { - --pf-c-progress-stepper__step-icon--Color: var(--pf-global--success-color--100, #3e8635); -} - -:host([variant="info"]) { - --pf-c-progress-stepper__step-icon--Color: var(--pf-global--info-color--100, #2b9af3); -} - -:host([variant="warning"]) { - --pf-c-progress-stepper__step-icon--Color: var(--pf-global--warning-color--100, #f0ab00); -} - -:host([variant="danger"]) { - --pf-c-progress-stepper__step-icon--Color: var(--pf-global--danger-color--100, #c9190b); - --pf-c-progress-stepper__step-title--Color: var(--pf-c-progress-stepper__step--m-danger__step-title--Color); - --pf-c-progress-stepper__step-title--m-help-text--hover--Color: var(--pf-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--Color); - --pf-c-progress-stepper__step-title--m-help-text--focus--Color: var(--pf-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--Color); - --pf-c-progress-stepper__step-title--m-help-text--TextDecorationColor: var(--pf-c-progress-stepper__step--m-danger__step-title--m-help-text--TextDecorationColor); - --pf-c-progress-stepper__step-title--m-help-text--hover--TextDecorationColor: var(--pf-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--TextDecorationColor); - --pf-c-progress-stepper__step-title--m-help-text--focus--TextDecorationColor: var(--pf-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--TextDecorationColor); -} diff --git a/elements/pf-progress-stepper/pf-progress-step.ts b/elements/pf-progress-stepper/pf-progress-step.ts deleted file mode 100644 index 6ebe9e2a1f..0000000000 --- a/elements/pf-progress-stepper/pf-progress-step.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PropertyValues, TemplateResult } from 'lit'; -import type { PfProgressStepper } from './pf-progress-stepper.js'; - -import { LitElement, html } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import style from './pf-progress-step.css'; - -const ICONS = new Map(Object.entries({ - success: { icon: 'check-circle' }, - danger: { icon: 'exclamation-circle' }, - warning: { icon: 'exclamation-triangle' }, - info: { icon: 'resources-full', set: 'patternfly' }, -})); - -/** - * @slot - - * Short description of the current step. - * @slot description - * Longer description of the current step. - * @slot icon - * Overrides the icon property - */ -@customElement('pf-progress-step') -export class PfProgressStep extends LitElement { - protected static parentTagName = 'pf-progress-stepper'; - - static readonly styles: CSSStyleSheet[] = [style]; - - /** Optional extended description of the step */ - @property() description?: string; - - /** Step Icon */ - @property() icon?: string; - - /** Step icon set */ - @property({ attribute: 'icon-set' }) iconSet?: string; - - /** Describes the state of the current item */ - @property({ reflect: true }) variant?: 'pending' | 'info' | 'success' | 'warning' | 'danger'; - - /** Indicates if this item is the current active item. */ - @property({ type: Boolean, reflect: true }) current = false; - - #slots = new SlotController(this, 'title', 'description'); - - #internals = InternalsController.of(this, { role: 'listitem' }); - - render(): TemplateResult<1> { - const hasDescription = !!(this.description ?? this.#slots.hasSlotted('description')); - const icon = this.icon ?? ICONS.get(this.variant ?? 'default')?.icon; - const set = this.iconSet ?? ICONS.get(this.variant ?? 'default')?.set; - const { parentTagName } = (this.constructor as typeof PfProgressStep); - const { compact = false } = this.closest?.(parentTagName) ?? {}; - return html` -
- - - -
-
- - ${this.description} -
- `; - } - - updated(changed: PropertyValues): void { - if (changed.has('current')) { - this.#internals.ariaCurrent = String(!!this.current); - } - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-progress-step': PfProgressStep; - } -} diff --git a/elements/pf-progress-stepper/pf-progress-stepper.css b/elements/pf-progress-stepper/pf-progress-stepper.css deleted file mode 100644 index 6e96c6bc12..0000000000 --- a/elements/pf-progress-stepper/pf-progress-stepper.css +++ /dev/null @@ -1,324 +0,0 @@ -[hidden] { - display: none !important; -} - -:host { - /** Grid auto flow for vertical layout */ - --pf-c-progress-stepper--m-vertical--GridAutoFlow: row; - /** Grid template columns for vertical layout */ - --pf-c-progress-stepper--m-vertical--GridTemplateColumns: auto 1fr; - /** Top position for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--Top: 0; - /** Left position for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--Left: calc(var(--pf-c-progress-stepper__step-icon--Width) / 2); - /** Width for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--Width: auto; - /** Height for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--Height: 100%; - /** Right border width for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--BorderRightWidth: var(--pf-global--BorderWidth--md, 2px); - /** Right border color for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--BorderRightColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** Bottom border width for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--BorderBottomWidth: 0; - /** Bottom border color for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--BorderBottomColor: transparent; - /** Transform for vertical step connector pseudo-element */ - --pf-c-progress-stepper--m-vertical__step-connector--before--Transform: translateX(-50%); - /** Top margin for vertical step main content */ - --pf-c-progress-stepper--m-vertical__step-main--MarginTop: var(--pf-global--spacer--xs, 0.25rem); - /** Right margin for vertical step main content */ - --pf-c-progress-stepper--m-vertical__step-main--MarginRight: 0; - /** Bottom margin for vertical step main content */ - --pf-c-progress-stepper--m-vertical__step-main--MarginBottom: var(--pf-global--spacer--xl, 2rem); - /** Left margin for vertical step main content */ - --pf-c-progress-stepper--m-vertical__step-main--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - /** Grid template columns for vertical compact layout */ - --pf-c-progress-stepper--m-vertical--m-compact--GridTemplateColumns: 1fr; - /** Padding bottom for vertical compact step connector */ - --pf-c-progress-stepper--m-vertical--m-compact__step-connector--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - /** Grid row for vertical compact step connector */ - --pf-c-progress-stepper--m-vertical--m-compact__step-connector--GridRow: auto; - /** Right margin for vertical compact step main content */ - --pf-c-progress-stepper--m-vertical--m-compact__step-main--MarginRight: 0; - /** Right margin for vertical centered step main content */ - --pf-c-progress-stepper--m-vertical--m-center__step-main--MarginRight: 0; - /** Left margin for vertical centered step main content */ - --pf-c-progress-stepper--m-vertical--m-center__step-main--MarginLeft: 0; - /** Grid auto flow for horizontal layout */ - --pf-c-progress-stepper--m-horizontal--GridAutoFlow: column; - /** Grid template columns for horizontal layout */ - --pf-c-progress-stepper--m-horizontal--GridTemplateColumns: initial; - /** Top position for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--Top: calc(var(--pf-c-progress-stepper__step-icon--Height) / 2); - /** Left position for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--Left: 0; - /** Width for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--Width: 100%; - /** Height for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--Height: auto; - /** Right border width for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--BorderRightWidth: 0; - /** Right border color for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--BorderRightColor: unset; - /** Bottom border width for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - /** Bottom border color for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** Transform for horizontal step connector pseudo-element */ - --pf-c-progress-stepper--m-horizontal__step-connector--before--Transform: translateY(-50%); - /** Top margin for horizontal step main content */ - --pf-c-progress-stepper--m-horizontal__step-main--MarginTop: var(--pf-global--spacer--xs, 0.25rem); - /** Right margin for horizontal step main content */ - --pf-c-progress-stepper--m-horizontal__step-main--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - /** Bottom margin for horizontal step main content */ - --pf-c-progress-stepper--m-horizontal__step-main--MarginBottom: 0; - /** Left margin for horizontal step main content */ - --pf-c-progress-stepper--m-horizontal__step-main--MarginLeft: 0; - /** Grid template columns for horizontal compact layout */ - --pf-c-progress-stepper--m-horizontal--m-compact--GridTemplateColumns: repeat(auto-fill, 1.75rem); - /** Padding bottom for horizontal compact step connector */ - --pf-c-progress-stepper--m-horizontal--m-compact__step-connector--PaddingBottom: 0; - /** Grid row for horizontal compact step connector */ - --pf-c-progress-stepper--m-horizontal--m-compact__step-connector--GridRow: 2; - /** Grid auto flow for compact layout */ - --pf-c-progress-stepper--m-compact--GridAutoFlow: row; - /** Top margin for compact step main content */ - --pf-c-progress-stepper--m-compact__step-main--MarginTop: 0; - /** Bottom margin for compact step main content */ - --pf-c-progress-stepper--m-compact__step-main--MarginBottom: var(--pf-global--spacer--xs, 0.25rem); - /** Minimum width for compact step connector */ - --pf-c-progress-stepper--m-compact__step-connector--MinWidth: 1.75rem; - /** Width for compact step icon */ - --pf-c-progress-stepper--m-compact__step-icon--Width: 1.125rem; - /** Font size for compact step icon */ - --pf-c-progress-stepper--m-compact__step-icon--FontSize: var(--pf-global--icon--FontSize--sm, 0.625rem); - /** Font size for compact step title */ - --pf-c-progress-stepper--m-compact__step-title--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Font weight for compact step title */ - --pf-c-progress-stepper--m-compact__step-title--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** Top margin for compact pficon */ - --pf-c-progress-stepper--m-compact__pficon--MarginTop: 2px; - /** Top margin for compact exclamation triangle icon */ - --pf-c-progress-stepper--m-compact__fa-exclamation-triangle--MarginTop: -3px; - /** Left position for centered step connector pseudo-element */ - --pf-c-progress-stepper--m-center__step-connector--before--Left: 50%; - /** Grid template columns for centered layout */ - --pf-c-progress-stepper--m-center--GridTemplateColumns: 1fr; - /** Justify content for centered step connector */ - --pf-c-progress-stepper--m-center__step-connector--JustifyContent: center; - /** Right margin for centered step main content */ - --pf-c-progress-stepper--m-center__step-main--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - /** Left margin for centered step main content */ - --pf-c-progress-stepper--m-center__step-main--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - /** Text alignment for centered step main content */ - --pf-c-progress-stepper--m-center__step-main--TextAlign: center; - /** Right margin for centered step description */ - --pf-c-progress-stepper--m-center__step-description--MarginRight: 0; - /** Left margin for centered step description */ - --pf-c-progress-stepper--m-center__step-description--MarginLeft: 0; - /** Grid template rows */ - --pf-c-progress-stepper--GridTemplateRows: auto 1fr; - /** Justify content for step connector */ - --pf-c-progress-stepper__step-connector--JustifyContent: start; - /** Z-index for step icon */ - --pf-c-progress-stepper__step-icon--ZIndex: var(--pf-global--ZIndex--xs, 100); - /** Width of the step icon */ - --pf-c-progress-stepper__step-icon--Width: 1.75rem; - /** Height of the step icon */ - --pf-c-progress-stepper__step-icon--Height: var(--pf-c-progress-stepper__step-icon--Width); - /** Font size of the step icon */ - --pf-c-progress-stepper__step-icon--FontSize: var(--pf-global--FontSize--md, 1rem); - /** Color of the step icon */ - --pf-c-progress-stepper__step-icon--Color: var(--pf-global--Color--100, #151515); - /** Background color of the step icon */ - --pf-c-progress-stepper__step-icon--BackgroundColor: var(--pf-global--BackgroundColor--light-200, #fafafa); - /** Border width of the step icon */ - --pf-c-progress-stepper__step-icon--BorderWidth: var(--pf-global--BorderWidth--md, 2px); - /** Border color of the step icon */ - --pf-c-progress-stepper__step-icon--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); - /** Top margin for pficon */ - --pf-c-progress-stepper__pficon--MarginTop: 3px; - /** Top margin for exclamation triangle icon */ - --pf-c-progress-stepper__fa-exclamation-triangle--MarginTop: -5px; - /** Color of the step title */ - --pf-c-progress-stepper__step-title--Color: var(--pf-global--Color--100, #151515); - /** Text alignment of the step title */ - --pf-c-progress-stepper__step-title--TextAlign: left; - /** Font size of the step title */ - --pf-c-progress-stepper__step-title--FontSize: var(--pf-global--FontSize--md, 1rem); - /** Font weight of the step title */ - --pf-c-progress-stepper__step-title--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** Font weight for current step title */ - --pf-c-progress-stepper__step--m-current__step-title--FontWeight: var(--pf-global--FontWeight--bold, 700); - /** Color for current step title */ - --pf-c-progress-stepper__step--m-current__step-title--Color: var(--pf-global--Color--100, #151515); - /** Color for pending step title */ - --pf-c-progress-stepper__step--m-pending__step-title--Color: var(--pf-global--Color--200, #6a6e73); - /** Color for danger step title */ - --pf-c-progress-stepper__step--m-danger__step-title--Color: var(--pf-global--danger-color--100, #c9190b); - /** Text decoration color for help text step title */ - --pf-c-progress-stepper__step-title--m-help-text--TextDecorationColor: var(--pf-global--BorderColor--200, #8a8d90); - /** Text decoration thickness for help text step title */ - --pf-c-progress-stepper__step-title--m-help-text--TextDecorationThickness: var(--pf-global--BorderWidth--sm, 1px); - /** Text underline offset for help text step title */ - --pf-c-progress-stepper__step-title--m-help-text--TextUnderlineOffset: 0.25rem; - /** Text decoration color for help text step title on hover */ - --pf-c-progress-stepper__step-title--m-help-text--hover--TextDecorationColor: var(--pf-global--Color--100, #151515); - /** Text decoration color for help text step title on focus */ - --pf-c-progress-stepper__step-title--m-help-text--focus--TextDecorationColor: var(--pf-global--Color--100, #151515); - /** Color for help text step title on hover */ - --pf-c-progress-stepper__step-title--m-help-text--hover--Color: var(--pf-global--Color--100, #151515); - /** Color for help text step title on focus */ - --pf-c-progress-stepper__step-title--m-help-text--focus--Color: var(--pf-global--Color--100, #151515); - /** Color for danger help text step title on hover */ - --pf-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--Color: var(--pf-global--danger-color--200, #a30000); - /** Color for danger help text step title on focus */ - --pf-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--Color: var(--pf-global--danger-color--200, #a30000); - /** Text decoration color for danger help text step title */ - --pf-c-progress-stepper__step--m-danger__step-title--m-help-text--TextDecorationColor: var(--pf-global--danger-color--100, #c9190b); - /** Text decoration color for danger help text step title on hover */ - --pf-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--TextDecorationColor: var(--pf-global--danger-color--200, #a30000); - /** Text decoration color for danger help text step title on focus */ - --pf-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--TextDecoerationColor: var(--pf-global--danger-color--200, #a30000); - /** Top margin for step description */ - --pf-c-progress-stepper__step-description--MarginTop: var(--pf-global--spacer--xs, 0.25rem); - /** Font size of the step description */ - --pf-c-progress-stepper__step-description--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Color of the step description */ - --pf-c-progress-stepper__step-description--Color: var(--pf-global--Color--200, #6a6e73); - /** Text alignment of the step description */ - --pf-c-progress-stepper__step-description--TextAlign: left; - /** Grid auto flow (defaults to vertical) */ - --pf-c-progress-stepper--GridAutoFlow: var(--pf-c-progress-stepper--m-vertical--GridAutoFlow); - /** Grid template columns (defaults to vertical) */ - --pf-c-progress-stepper--GridTemplateColumns: var(--pf-c-progress-stepper--m-vertical--GridTemplateColumns); - /** Top position for step connector pseudo-element (defaults to vertical) */ - --pf-c-progress-stepper__step-connector--before--Top: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Top); - --pf-c-progress-stepper__step-connector--before--Left: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Left); - --pf-c-progress-stepper__step-connector--before--Width: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Width); - --pf-c-progress-stepper__step-connector--before--Height: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Height); - --pf-c-progress-stepper__step-connector--before--BorderRightWidth: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderRightWidth); - --pf-c-progress-stepper__step-connector--before--BorderRightColor: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderRightColor); - --pf-c-progress-stepper__step-connector--before--BorderBottomWidth: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderBottomWidth); - --pf-c-progress-stepper__step-connector--before--BorderBottomColor: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderBottomColor); - --pf-c-progress-stepper__step-connector--before--Transform: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Transform); - --pf-c-progress-stepper__step-main--MarginTop: var(--pf-c-progress-stepper--m-vertical__step-main--MarginTop); - --pf-c-progress-stepper__step-main--MarginRight: var(--pf-c-progress-stepper--m-vertical__step-main--MarginRight); - --pf-c-progress-stepper__step-main--MarginBottom: var(--pf-c-progress-stepper--m-vertical__step-main--MarginBottom); - --pf-c-progress-stepper__step-main--MarginLeft: var(--pf-c-progress-stepper--m-vertical__step-main--MarginLeft); - --pf-c-progress-stepper--m-compact--GridTemplateColumns: var(--pf-c-progress-stepper--m-vertical--m-compact--GridTemplateColumns); - --pf-c-progress-stepper--m-compact__step-connector--GridRow: var(--pf-c-progress-stepper--m-vertical--m-compact__step-connector--GridRow); - --pf-c-progress-stepper--m-compact__step-connector--PaddingBottom: var(--pf-c-progress-stepper--m-vertical--m-compact__step-connector--PaddingBottom); - --pf-c-progress-stepper--m-center__step-connector--before--Content: none; - --pf-c-progress-stepper--m-center__step-main--before--Content: ''; - --pf-c-progress-stepper__step-connector--before--Content: ''; - --pf-c-progress-stepper__step-main--before--Content: none; - position: relative; - display: grid; - grid-auto-flow: var(--pf-c-progress-stepper--GridAutoFlow); - grid-template-columns: var(--pf-c-progress-stepper--GridTemplateColumns); - grid-template-rows: var(--pf-c-progress-stepper--GridTemplateRows); - grid-auto-columns: 1fr; -} - -@media (min-width: 768px) { - :host { - --pf-c-progress-stepper--GridAutoFlow: var(--pf-c-progress-stepper--m-horizontal--GridAutoFlow, column); - --pf-c-progress-stepper--GridTemplateColumns: var(--pf-c-progress-stepper--m-horizontal--GridTemplateColumns, initial); - --pf-c-progress-stepper__step-connector--before--Top: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--Top); - --pf-c-progress-stepper__step-connector--before--Left: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--Left); - --pf-c-progress-stepper__step-connector--before--Width: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--Width); - --pf-c-progress-stepper__step-connector--before--Height: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--Height); - --pf-c-progress-stepper__step-connector--before--BorderRightWidth: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--BorderRightWidth); - --pf-c-progress-stepper__step-connector--before--BorderRightColor: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--BorderRightColor); - --pf-c-progress-stepper__step-connector--before--BorderBottomWidth: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomWidth); - --pf-c-progress-stepper__step-connector--before--BorderBottomColor: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomColor); - --pf-c-progress-stepper__step-connector--before--Transform: var(--pf-c-progress-stepper--m-horizontal__step-connector--before--Transform); - --pf-c-progress-stepper__step-main--MarginTop: var(--pf-c-progress-stepper--m-horizontal__step-main--MarginTop); - --pf-c-progress-stepper__step-main--MarginRight: var(--pf-c-progress-stepper--m-horizontal__step-main--MarginRight); - --pf-c-progress-stepper__step-main--MarginBottom: var(--pf-c-progress-stepper--m-horizontal__step-main--MarginBottom); - --pf-c-progress-stepper__step-main--MarginLeft: var(--pf-c-progress-stepper--m-horizontal__step-main--MarginLeft); - --pf-c-progress-stepper--m-compact--GridTemplateColumns: var(--pf-c-progress-stepper--m-horizontal--m-compact--GridTemplateColumns); - --pf-c-progress-stepper--m-compact__step-connector--GridRow: var(--pf-c-progress-stepper--m-horizontal--m-compact__step-connector--GridRow); - --pf-c-progress-stepper--m-compact__step-connector--PaddingBottom: var(--pf-c-progress-stepper--m-horizontal--m-compact__step-connector--PaddingBottom); - --pf-c-progress-stepper__step-connector--before--Content: var(--pf-c-progress-stepper--m-center__step-connector--before--Content); - --pf-c-progress-stepper__step-main--before--Content: var(--pf-c-progress-stepper--m-center__step-main--before--Content); - --pf-c-progress-stepper--m-center__step-connector--before--Content: ''; - --pf-c-progress-stepper--m-center__step-main--before--Content: none; - } -} - -:host([center]) { - --pf-c-progress-stepper__step-connector--JustifyContent: var(--pf-c-progress-stepper--m-center__step-connector--JustifyContent); - --pf-c-progress-stepper__step-main--MarginRight: var(--pf-c-progress-stepper--m-center__step-main--MarginRight); - --pf-c-progress-stepper__step-main--MarginLeft: var(--pf-c-progress-stepper--m-center__step-main--MarginLeft); - --pf-c-progress-stepper--step-main--TextAlign: var(--pf-c-progress-stepper--m-center__step-main--TextAlign, auto); - --pf-c-progress-stepper__step-title--TextAlign: var(--pf-c-progress-stepper--m-center__step-title--TextAlign, auto); - --pf-c-progress-stepper__step-description--MarginRight: var(--pf-c-progress-stepper--m-center__step-description--MarginRight); - --pf-c-progress-stepper__step-description--MarginLeft: var(--pf-c-progress-stepper--m-center__step-description--MarginLeft); - --pf-c-progress-stepper__step-description--TextAlign: var(--pf-c-progress-stepper--m-center__step-description--TextAlign, auto); - --pf-c-progress-stepper--m-vertical__step-main--MarginRight: var(--pf-c-progress-stepper--m-vertical--m-center__step-main--MarginRight); - --pf-c-progress-stepper--m-vertical__step-main--MarginLeft: var(--pf-c-progress-stepper--m-vertical--m-center__step-main--MarginLeft); - --pf-c-progress-stepper__step-connector--before--Content: var(--pf-c-progress-stepper--m-center__step-connector--before--Content); - --pf-c-progress-stepper__step-main--before--Content: var(--pf-c-progress-stepper--m-center__step-main--before--Content); - --pf-c-progress-stepper__step-connector--before--Left: var(--pf-c-progress-stepper--m-center__step-connector--before--Left); - - grid-template-columns: var(--pf-c-progress-stepper--m-center--GridTemplateColumns); -} - -:host([center]:not([compact])) { - --pf-c-progress-stepper__step-main--before--Content: var(--pf-c-progress-stepper--m-center__step-main--before--Content); - --pf-c-progress-stepper__step-connector--before--Content: var(--pf-c-progress-stepper--m-center__step-connector--before--Content); -} - -:host([vertical]) ::slotted(pf-progress-step) { - flex-direction: row; - align-items: flex-start; -} - -:host([vertical]) { - --pf-c-progress-stepper--GridAutoFlow: var(--pf-c-progress-stepper--m-vertical--GridAutoFlow); - --pf-c-progress-stepper--GridTemplateColumns: var(--pf-c-progress-stepper--m-vertical--GridTemplateColumns); - --pf-c-progress-stepper__step-connector--before--Top: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Top); - --pf-c-progress-stepper__step-connector--before--Left: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Left); - --pf-c-progress-stepper__step-connector--before--Width: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Width); - --pf-c-progress-stepper__step-connector--before--Height: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Height); - --pf-c-progress-stepper__step-connector--before--BorderRightWidth: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderRightWidth); - --pf-c-progress-stepper__step-connector--before--BorderRightColor: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderRightColor); - --pf-c-progress-stepper__step-connector--before--BorderBottomWidth: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderBottomWidth); - --pf-c-progress-stepper__step-connector--before--BorderBottomColor: var(--pf-c-progress-stepper--m-vertical__step-connector--before--BorderBottomColor); - --pf-c-progress-stepper__step-connector--before--Transform: var(--pf-c-progress-stepper--m-vertical__step-connector--before--Transform); - --pf-c-progress-stepper__step-main--MarginTop: var(--pf-c-progress-stepper--m-vertical__step-main--MarginTop); - --pf-c-progress-stepper__step-main--MarginRight: var(--pf-c-progress-stepper--m-vertical__step-main--MarginRight); - --pf-c-progress-stepper__step-main--MarginBottom: var(--pf-c-progress-stepper--m-vertical__step-main--MarginBottom); - --pf-c-progress-stepper__step-main--MarginLeft: var(--pf-c-progress-stepper--m-vertical__step-main--MarginLeft); - --pf-c-progress-stepper--m-compact--GridTemplateColumns: var(--pf-c-progress-stepper--m-vertical--m-compact--GridTemplateColumns); - --pf-c-progress-stepper--m-compact__step-connector--GridRow: var(--pf-c-progress-stepper--m-vertical--m-compact__step-connector--GridRow); - --pf-c-progress-stepper--m-compact__step-connector--PaddingBottom: var(--pf-c-progress-stepper--m-vertical--m-compact__step-connector--PaddingBottom); - --pf-c-progress-stepper__step-connector--before--Content: ''; - --pf-c-progress-stepper__step-main--before--Content: none; - --pf-c-progress-stepper--m-center__step-connector--before--Content: none; - --pf-c-progress-stepper--m-center__step-main--before--Content: ''; - --pf-c-progress-stepper__step-main--Position: relative; -} - -:host([vertical][center]) { - --pf-c-progress-stepper__step-connector--before--Left: var(--pf-c-progress-stepper--m-center__step-connector--before--Left); -} - -:host([compact]) { - --pf-c-progress-stepper__step-main--MarginTop: var(--pf-c-progress-stepper--m-compact__step-main--MarginTop); - --pf-c-progress-stepper__step-main--MarginBottom: var(--pf-c-progress-stepper--m-compact__step-main--MarginBottom); - --pf-c-progress-stepper__step-icon--Width: var(--pf-c-progress-stepper--m-compact__step-icon--Width); - --pf-c-progress-stepper__step-icon--FontSize: var(--pf-c-progress-stepper--m-compact__step-icon--FontSize); - --pf-c-progress-stepper__step-title--FontSize: var(--pf-c-progress-stepper--m-compact__step-title--FontSize); - --pf-c-progress-stepper__step--m-current__step-title--FontWeight: var(--pf-c-progress-stepper--m-compact__step-title--FontWeight); - --pf-c-progress-stepper__pficon--MarginTop: var(--pf-c-progress-stepper--m-compact__pficon--MarginTop); - --pf-c-progress-stepper__fa-exclamation-triangle--MarginTop: var(--pf-c-progress-stepper--m-compact__fa-exclamation-triangle--MarginTop); - --pf-c-progress-stepper--m-vertical__step-main--MarginLeft: var(--pf-c-progress-stepper--m-vertical--m-compact__step-main--MarginLeft); - --pf-c-progress-stepper--m-vertical__step-main--MarginRight: var(--pf-c-progress-stepper--m-vertical--m-compact__step-main--MarginRight); - display: inline-grid; - grid-template-columns: var(--pf-c-progress-stepper--m-compact--GridTemplateColumns); - grid-auto-flow: var(--pf-c-progress-stepper--m-compact--GridAutoFlow); -} diff --git a/elements/pf-progress-stepper/pf-progress-stepper.ts b/elements/pf-progress-stepper/pf-progress-stepper.ts deleted file mode 100644 index e82097e188..0000000000 --- a/elements/pf-progress-stepper/pf-progress-stepper.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import style from './pf-progress-stepper.css'; - -import { PfProgressStep } from './pf-progress-step.js'; -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import '@patternfly/elements/pf-icon/pf-icon.js'; - -/** - * A **progress stepper** displays a timeline of tasks in a workflow and tracks the user's current progress through this workflow. - * @alias Progress Stepper - */ -@customElement('pf-progress-stepper') -export class PfProgressStepper extends LitElement { - protected static childTagName = 'pf-progress-step'; - - static readonly styles: CSSStyleSheet[] = [style]; - - static formAssociated = true; - - /** Whether to use the vertical layout */ - @property({ type: Boolean, reflect: true }) vertical = false; - - /** Whether to use the center alignment */ - @property({ type: Boolean, reflect: true }) center = false; - - /** Whether to use the compact layout */ - @property({ type: Boolean, reflect: true }) compact = false; - - #internals = InternalsController.of(this, { - role: 'progressbar', - ariaValueNow: this.value.toString(), - }); - - #mo = new MutationObserver(() => this.#onMutation()); - - get value(): number { - const { childTagName } = (this.constructor as typeof PfProgressStepper); - const steps = this.querySelectorAll?.(childTagName) ?? []; - const current = this.querySelector?.(`${childTagName}[current]`); - const n = Array.from(steps).indexOf(current as PfProgressStep) + 1; - return (n / steps.length) * 100; - } - - constructor() { - super(); - this.#mo.observe(this, { childList: true }); - } - - #onMutation() { - this.#internals.ariaValueNow = this.value.toString(); - } - - render(): TemplateResult<1> { - // TODO: add label prop - // eslint-disable-next-line lit-a11y/accessible-name - return html`
`; - } - - updated(changed: PropertyValues): void { - if (changed.has('compact')) { - this.querySelectorAll?.('pf-progress-step').forEach(step => step.requestUpdate()); - } - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-progress-stepper': PfProgressStepper; - } -} diff --git a/elements/pf-progress-stepper/test/pf-progress-stepper.spec.ts b/elements/pf-progress-stepper/test/pf-progress-stepper.spec.ts deleted file mode 100644 index 9fd315145b..0000000000 --- a/elements/pf-progress-stepper/test/pf-progress-stepper.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { html, expect } from '@open-wc/testing'; - -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; - -import { PfProgressStepper } from '@patternfly/elements/pf-progress-stepper/pf-progress-stepper.js'; -import { PfProgressStep } from '../pf-progress-step.js'; - -describe('', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-progress-stepper')).to.be.an.instanceof(PfProgressStepper); - expect(document.createElement('pf-progress-step')).to.be.an.instanceof(PfProgressStep); - }); - - it('it should upgrade', async function() { - const el = await createFixture(html``); - expect(el) - .to.be.an.instanceOf(customElements.get('pf-progress-stepper')) - .and - .to.be.an.instanceOf(PfProgressStepper); - }); -}); diff --git a/elements/pf-progress/README.md b/elements/pf-progress/README.md deleted file mode 100644 index b03913e672..0000000000 --- a/elements/pf-progress/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Progress - -A progress bar gives the user a visual representation of their completion status of an ongoing process or task. - -Read more about Progress in the [PatternFly Elements Progress documentation][docs]. - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-progress/pf-progress.js'; -``` - -## Usage - -```html - -``` - -[docs]: https://patternflyelements.org/components/progress diff --git a/elements/pf-progress/demo/index.html b/elements/pf-progress/demo/index.html deleted file mode 100644 index 76f32e9ec3..0000000000 --- a/elements/pf-progress/demo/index.html +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/elements/pf-progress/demo/kitchen-sink.css b/elements/pf-progress/demo/kitchen-sink.css deleted file mode 100644 index 6271c537ff..0000000000 --- a/elements/pf-progress/demo/kitchen-sink.css +++ /dev/null @@ -1,4 +0,0 @@ -pf-progress { - padding-bottom: 0.25rem; - display: block; -} \ No newline at end of file diff --git a/elements/pf-progress/demo/kitchen-sink.html b/elements/pf-progress/demo/kitchen-sink.html deleted file mode 100644 index 36c0dd7d80..0000000000 --- a/elements/pf-progress/demo/kitchen-sink.html +++ /dev/null @@ -1,141 +0,0 @@ - - - -

pf-progress

- -

Default States

- - - -

Value

- - -

Description

- - -

Aria-label

- - -

Max

- - -

Min

- - -

Size (sm, lg)

- - - -

Measure Location (Inside, Outside, None)

- - - - -

Variant (Sucess, danger, warning)

- - - - -

Variant (Success, Danger, Warning) and Size (sm, lg)

- - - - - - - -

Variant (Success, Danger, Warning) and Measure Location (Inside, Outside, None)

- - - - - - - - - - -

Variant (Success, Danger, Warning), Size (sm, lg) and Measure Location (Inside, Outside, None)

- - - - - - - - - - - - - - - - - - - -

Label w/ no description

- -

Value

- - - - -

Size (sm, lg)

- - - - - - - -

Measure Location (Inside, Outside)

- - - - - - - -

Variant (Sucess, danger, warning)

- - - - - - - - - - -

Variant (Success, Danger, Warning) and Size (sm, lg)

- - - - - - - - - - - - - - - - - - - -

Truncated description

- - - diff --git a/elements/pf-progress/demo/truncated-description.html b/elements/pf-progress/demo/truncated-description.html deleted file mode 100644 index 72d7e9f0f3..0000000000 --- a/elements/pf-progress/demo/truncated-description.html +++ /dev/null @@ -1,20 +0,0 @@ -
- -
- - - - diff --git a/elements/pf-progress/docs/pf-progress.md b/elements/pf-progress/docs/pf-progress.md deleted file mode 100644 index 5214bf4444..0000000000 --- a/elements/pf-progress/docs/pf-progress.md +++ /dev/null @@ -1,49 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - -{% endrenderOverview %} - -{% band header="Usage" %} - - ### Success variant - {% htmlexample %} - - {% endhtmlexample %} - - ### Inside measurement - {% htmlexample %} - - {% endhtmlexample %} - - ### Large size - {% htmlexample %} - - {% endhtmlexample %} - - ### Inside measurement - {% htmlexample %} - - {% endhtmlexample %} - - ### Truncated Description - {% htmlexample %} - - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-progress/pf-progress.css b/elements/pf-progress/pf-progress.css deleted file mode 100644 index dd32a0e915..0000000000 --- a/elements/pf-progress/pf-progress.css +++ /dev/null @@ -1,229 +0,0 @@ -* { - box-sizing: border-box; -} - -#container { - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: #0066cc33; /* WARNING: not a recognized token value */ - --_pf-c-progress--m-success__bar--BackgroundColorWithOpacity: #3e863533; /* WARNING: not a recognized token value */ - --_pf-c-progress--m-warning__bar--BackgroundColorWithOpacity: #f0ab0033; /* WARNING: not a recognized token value */ - --_pf-c-progress--m-danger__bar--BackgroundColorWithOpacity: #c9190b33; /* WARNING: not a recognized token value */ - - /** Gap between sections of progress bar */ - --pf-c-progress--GridGap: var(--pf-global--spacer--md, 1rem); - /** Color of progress bar */ - --pf-c-progress__bar--before--BackgroundColor: var(--pf-global--primary-color--100, #0066cc); - /** Height of progress bar */ - --pf-c-progress__bar--Height: var(--pf-global--spacer--md, 1rem); - /** Background color of progress bar */ - --pf-c-progress__bar--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #ffffff); - /** Color of status icon */ - --pf-c-progress__status-icon--Color: var(--pf-global--Color--100, #151515); - /** Margin left of status icon */ - --pf-c-progress__status-icon--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); - /** Height of progress bar indicator */ - --pf-c-progress__indicator--Height: var(--pf-c-progress__bar--Height); - /** Background color of progress bar indicator */ - --pf-c-progress__indicator--BackgroundColor: var(--pf-c-progress__bar--before--BackgroundColor); - /** Background color of progress bar when variant is success */ - --pf-c-progress--m-success__bar--BackgroundColor: var(--pf-global--success-color--100, #3e8635); - /** Background color of progress bar when variant is warning */ - --pf-c-progress--m-warning__bar--BackgroundColor: var(--pf-global--warning-color--100, #f0ab00); - /** Background color of progress bar when variant is danger */ - --pf-c-progress--m-danger__bar--BackgroundColor: var(--pf-global--danger-color--100, #c9190b); - /** Color of status icon when variant is success */ - --pf-c-progress--m-success__status-icon--Color: var(--pf-global--success-color--100, #3e8635); - /** Color of status icon when variant is warning */ - --pf-c-progress--m-warning__status-icon--Color: var(--pf-global--warning-color--100, #f0ab00); - /** Color of status icon when variant is danger */ - --pf-c-progress--m-danger__status-icon--Color: var(--pf-global--danger-color--100, #c9190b); - /** Color of progress bar measure when variant is success and measure location is inside */ - --pf-c-progress--m-success--m-inside__measure--Color: var(--pf-global--Color--light-100, #ffffff); - /** Font size of progress bar measure when measure location is outside */ - --pf-c-progress--m-outside__measure--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Height of progress bar when size is small */ - --pf-c-progress--m-sm__bar--Height: var(--pf-global--spacer--sm, 0.5rem); - /** Font size of progress bar description when size is small */ - --pf-c-progress--m-sm__description--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Height of progress bar when size is large */ - --pf-c-progress--m-lg__bar--Height: var(--pf-global--spacer--lg, 1.5rem); - display: grid; - align-items: end; - grid-gap: var(--pf-c-progress--GridGap); - grid-template-columns: 1fr auto; - grid-template-rows: 1fr auto; - width: 100%; -} - -.sm { - --pf-c-progress__bar--Height: var(--pf-c-progress--m-sm__bar--Height); - --pf-c-progress__indicator--Height: var(--pf-c-progress--m-sm__bar--Height); -} - -.sm #description { - font-size: var(--pf-c-progress--m-sm__description--FontSize); -} - -.lg { - --pf-c-progress__bar--Height: var(--pf-c-progress--m-lg__bar--Height); - --pf-c-progress__indicator--Height: var(--pf-c-progress--m-lg__bar--Height); -} - -.outside #description { - grid-column: 1/3; -} - -.outside #status { - grid-column: 2/3; - grid-row: 2/3; - align-self: center; -} - -.outside progress, -.outside span { - display: inline-block; - font-size: var(--pf-c-progress--m-outside__measure--FontSize); - grid-column: 1/2; -} - -.singleline { - grid-template-rows: 1fr; -} - -.singleline #description { - display: none; - visibility: hidden; -} - -.singleline progress, -.singleline span { - grid-row: 1/2; - grid-column: 1/2; -} - -.singleline #status { - grid-row: 1/2; - grid-column: 2/3; -} - -.outside, .singleline { - grid-template-columns: 1fr fit-content(50%); -} - -#container.success { - --pf-c-progress__bar--before--BackgroundColor: var(--pf-c-progress--m-success__bar--BackgroundColor); - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-success__bar--BackgroundColorWithOpacity); - --pf-c-progress__status-icon--Color: var(--pf-c-progress--m-success__status-icon--Color); -} - -#container.warning { - --pf-c-progress__bar--before--BackgroundColor: var(--pf-c-progress--m-warning__bar--BackgroundColor); - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-warning__bar--BackgroundColorWithOpacity); - --pf-c-progress__status-icon--Color: var(--pf-c-progress--m-warning__status-icon--Color); -} - -#container.danger { - --pf-c-progress__bar--before--BackgroundColor: var(--pf-c-progress--m-danger__bar--BackgroundColor); - --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-danger__bar--BackgroundColorWithOpacity); - --pf-c-progress__status-icon--Color: var(--pf-c-progress--m-danger__status-icon--Color); -} - -#description { - word-break: break-word; - grid-column: 1/2; -} - -.descriptionTruncated #description { - overflow-x: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#status { - grid-column: 2/3; - grid-row: 1/2; - text-align: right; - word-break: break-word; - display: flex; - align-items: center; - justify-content: end; -} - -pf-icon { - margin-left: var(--pf-c-progress__status-icon--MarginLeft); - color: var(--pf-c-progress__status-icon--Color); -} - -progress { - position: relative; - grid-column: 1/3; - grid-row: 2/3; - align-self: center; - height: var(--pf-c-progress__bar--Height); - background-color: var(--pf-c-progress__bar--BackgroundColor); -} - -.indicator { - position: absolute; - top: 0; - left: 0; - height: var(--pf-c-progress__indicator--Height); - background-color: var(--pf-c-progress__indicator--BackgroundColor); -} - -.indicator { - width: 100%; - height: var(--pf-c-progress__bar--Height); - - display: block; -} - -span { - grid-column: 1/3; - grid-row: 2/3; - text-align: center; - color: var(--pf-c-progress--m-success--m-inside__measure--Color); -} - -span::after { - content: attr(data-value); - position: relative; - height: 100%; -} - -progress[value] { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - - background: var(--_pf-c-progress__bar--before--BackgroundColorWithOpacity); - - width: 100%; - height: var(--pf-c-progress__bar--Height); -} - -progress:not([value]) { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -progress[value]::-webkit-progress-bar { - background: var(--_pf-c-progress__bar--before--BackgroundColorWithOpacity); -} - -progress[value]::-moz-progress-bar { - background: var(--pf-c-progress__bar--before--BackgroundColor); -} - -progress[value]::-webkit-progress-value { - background-size: 100% 100%; - background-image: linear-gradient( - 90deg, - var(--pf-c-progress__bar--before--BackgroundColor) 100%, - var(--pf-c-progress__bar--before--BackgroundColor) 100% - ); -} - -pf-tooltip { - height: 0.01px; -} diff --git a/elements/pf-progress/pf-progress.ts b/elements/pf-progress/pf-progress.ts deleted file mode 100644 index 973b8d02d9..0000000000 --- a/elements/pf-progress/pf-progress.ts +++ /dev/null @@ -1,136 +0,0 @@ -import type { PropertyValues, TemplateResult } from 'lit'; -import { LitElement, html } 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 { styleMap } from 'lit/directives/style-map.js'; - -import styles from './pf-progress.css'; - -const ICONS = new Map(Object.entries({ - success: { icon: 'circle-check' }, - danger: { icon: 'circle-xmark' }, - warning: { icon: 'triangle-exclamation' }, -})); - -/** - * A progress bar gives the user a visual representation of their completion status of an ongoing process or task. - * @summary Display completion status of ongoing process or task. - * @alias Progress - */ -@customElement('pf-progress') -export class PfProgress extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - #internals = this.attachInternals(); - - /** Represents the value of the progress bar */ - @property({ reflect: true, type: Number }) value = 0; - - /** Description (title) above the progress bar */ - @property() description?: string; - - /** Indicate whether to truncate the string description (title) */ - @property({ - type: Boolean, - reflect: true, - attribute: 'description-truncated', - }) descriptionTruncated = false; - - /** Maximum value for the progress bar */ - @property({ type: Number, reflect: true }) max = 100; - - /** Minimum value for the progress bar */ - @property({ type: Number, reflect: true }) min = 0; - - /** Size of the progress bar (height) */ - @property() size?: 'sm' | 'lg'; - - /** Where the percentage will be displayed with the progress element */ - @property({ attribute: 'measure-location' }) measureLocation?: 'outside' | 'inside' | 'none'; - - /** Variant of the progress bar */ - @property() variant?: 'success' | 'danger' | 'warning'; - - get #calculatedPercentage(): number { - const { value, min, max } = this; - const percentage = Math.round((value - min) / (max - min) * 100); - if (Number.isNaN(percentage) || percentage < 0) { - return 0; - } - return Math.min(percentage, 100); - } - - get #icon() { - return ICONS.get(this.variant ?? '')?.icon; - } - - override willUpdate(changed: PropertyValues): void { - if (changed.has('value') || changed.has('min') || changed.has('max')) { - this.#internals.ariaValueNow = this.#calculatedPercentage.toString(); - } - if (this.#icon) { - import('@patternfly/elements/pf-icon/pf-icon.js'); - } - if (this.descriptionTruncated) { - import('@patternfly/elements/pf-tooltip/pf-tooltip.js'); - } - } - - render(): TemplateResult<1> { - const { size, measureLocation, variant, description, descriptionTruncated } = this; - const icon = this.#icon; - const singleLine = description?.length === 0; - const pct = this.#calculatedPercentage; - const width = `${pct}%`; - - return html` -
- - - - ${!descriptionTruncated ? '' : html` - - `} - - ${measureLocation === 'none' ? '' : html` - - `} - - - - ${measureLocation !== 'inside' ? '' : html` - - `} -
`; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-progress': PfProgress; - } -} diff --git a/elements/pf-progress/test/pf-progress.spec.ts b/elements/pf-progress/test/pf-progress.spec.ts deleted file mode 100644 index 3265c59949..0000000000 --- a/elements/pf-progress/test/pf-progress.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { expect, html, fixture } from '@open-wc/testing'; -import { PfProgress } from '@patternfly/elements/pf-progress/pf-progress.js'; - -describe('', function() { - let element: PfProgress; - - beforeEach(async function() { - element = await fixture(html` - - - `); - }); - - it('should upgrade', async function() { - const klass = customElements.get('pf-progress'); - expect(element).to.be.an.instanceOf(klass).and.to.be.an.instanceOf(PfProgress); - }); - - it('should be accessible', async function() { - await expect(element).shadowDom.to.be.accessible(); - }); - - it('should set the correct value on the progress bar', async function() { - const element = await fixture(html` - - - `); - expect(element.value).to.equal(33); - }); - - it('should set the correct title on the progress bar', async function() { - const element = await fixture(html` - - - `); - expect(element.title).to.equal('Progress title'); - }); - - it('should have the correct value with the max value set', async function() { - const max = Math.floor(Math.random() * 100); - const value = Math.floor(Math.random() * (max)); - - const element = await fixture(html` - - - `); - expect(element.value).to.equal(value); - expect(element.max).to.equal(max); - }); -}); diff --git a/elements/pf-search-input/README.md b/elements/pf-search-input/README.md deleted file mode 100644 index 3b4180e2ce..0000000000 --- a/elements/pf-search-input/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Search Input -A search input lets users type in words to find specific items or information. As they type, it can show matching results to help them quickly find what they are looking for. - -## Usage -A search input consists of a text field where users can type to find specific content or items. Unlike selects or dropdowns, which offer predefined options, a search input lets users enter their own keywords to filter or locate results. It includes a clear (×) button to easily remove the current input, allowing users to start a new search quickly. - -```html - - Alabama - New Jersey - New York - New Mexico - North Carolina - -``` diff --git a/elements/pf-search-input/demo/disabled.html b/elements/pf-search-input/demo/disabled.html deleted file mode 100644 index 14c47ff293..0000000000 --- a/elements/pf-search-input/demo/disabled.html +++ /dev/null @@ -1,34 +0,0 @@ -
-
- - Blue - Green - Magenta - Orange - Purple - Periwinkle - Pink - Red - Yellow - - Search -
-
- - - - \ No newline at end of file diff --git a/elements/pf-search-input/demo/index.html b/elements/pf-search-input/demo/index.html deleted file mode 100644 index 9a48121cba..0000000000 --- a/elements/pf-search-input/demo/index.html +++ /dev/null @@ -1,42 +0,0 @@ -
- - What is Red Hat Enterprise Linux? - How does Red Hat OpenShift work? - Why use Red Hat Ansible for automation? - Where can Red Hat OpenShift be deployed? - When should you use Red Hat Enterprise Linux? - What is Red Hat Satellite? - How does Red Hat integrate with AWS and other clouds? - Why choose Red Hat over other Linux vendors? - Where can I learn Red Hat technologies? - When does support end for RHEL versions? - What are Red Hat certifications? - How do you secure a RHEL server? - Why use OpenShift instead of vanilla Kubernetes? - Where is Red Hat headquartered? - When should you use Red Hat CoreOS? - What is Red Hat Insights? - How do you manage Red Hat subscriptions? - Why is RHEL considered enterprise-grade? - Where can I download RHEL for testing? - When was Red Hat founded? - -
- - - - \ No newline at end of file diff --git a/elements/pf-search-input/demo/pf-search-input-with-submit.html b/elements/pf-search-input/demo/pf-search-input-with-submit.html deleted file mode 100644 index 4282ef9e0f..0000000000 --- a/elements/pf-search-input/demo/pf-search-input-with-submit.html +++ /dev/null @@ -1,62 +0,0 @@ -
-
- - Alabama - New Jersey - New York - New Mexico - North Carolina - Alabama 1 - New Jersey 1 - New York 1 - New Mexico 1 - North Carolina 1 - Alabama 2 - New Jersey 2 - New York 2 - New Mexico 2 - North Carolina 2 - Alabama 3 - New Jersey 3 - New York 3 - New Mexico 3 - North Carolina 3 - - Search -
-
- - - - - \ No newline at end of file diff --git a/elements/pf-search-input/docs/pf-search-input.md b/elements/pf-search-input/docs/pf-search-input.md deleted file mode 100644 index 9a37f81ec5..0000000000 --- a/elements/pf-search-input/docs/pf-search-input.md +++ /dev/null @@ -1,91 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - - - -{% renderOverview %} - - Blue - Black - Brown - Bronze - Green - Magenta - Orange - Purple - Periwinkle - Pink - Red - Yellow - -{% endrenderOverview %} - -{% band header="Usage" %} - -#### Search Input - -{% htmlexample %} - {% renderFile "./elements/pf-search-input/demo/index.html" %} -{% endhtmlexample %} - -#### Search Input Form -{% htmlexample %} - {% renderFile "./elements/pf-search-input/demo/pf-search-input-with-submit.html" %} -{% endhtmlexample %} - -#### Disabled -{% htmlexample %} - {% renderFile "./elements/pf-search-input/demo/disabled.html" %} -{% endhtmlexample %} - -{% endband %} - -{% band header="Accessibility" %} - -The search input uses the [Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) recommendations from the WAI ARIA [Authoring Best Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg). - -When the dropdown is disabled it follows [WAI ARIA focusability recommendations](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols) for composite widget elements, where dropdown items are still focusable even when the dropdown is disabled. - -#### Toggle and typeahead input - -When focus is on the toggle, the following keyboard interactions apply: - -| Key | Function | -| ---------------------- | -------------------------------------------------------------------------------------- | -| Down Arrow | Opens the listbox and moves focus to the first listbox item. | -| Tab | Moves focus to the close button if visible; otherwise, moves to the next focusable element, then closes the listbox.| -| Shift + Tab | Moves focus out of element onto the previous focusable item and closes listbox. | - -#### Listbox options - -Listbox options use the [APG's Roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) recommendation. When focus is on the listbox, the following keyboard interactions apply: - -| Key | Function | -| ---------------------- | ------------------------------------------------------------------------------------- | -| Enter | Selects the options and closes the listbox. | -| Space | Selects the options and closes the listbox. | -| Tab | Moves focus out of element onto the next focusable options and closes listbox. | -| Shift + Tab | Moves focus to the toggle button and closes listbox. | -| Up Arrow | Moves focus to the previous option, optionally wrapping from the first to the last. | -| Down Arrow | Moves focus to the next option, optionally wrapping from the last to the first. | -| Left Arrow | Returns focus to the combobox without closing the popup and moves the input cursor one character to the left. If the input cursor is on the left-most character, the cursor does not move. | -| Right Arrow | Returns focus to the combobox without closing the popup and moves the input cursor one character to the right. If the input cursor is on the right-most character, the cursor does not move. | -| Escape | Close the listbox that contains focus and return focus to the input. | -| Any letter | Navigates to the next option that starts with the letter. | - -{% endband %} - -{% renderSlots for="pf-search-input", header="Slots on `pf-search-input`" %}{% endrenderSlots %} -{% renderAttributes for="pf-search-input", header="Attributes on `pf-search-input`" %}{% endrenderAttributes %} -{% renderMethods for="pf-search-input", header="Methods on `pf-search-input`" %}{% endrenderMethods %} -{% renderEvents for="pf-search-input", header="Events on `pf-search-input`" %}{% endrenderEvents %} -{% renderCssCustomProperties for="pf-search-input", header="CSS Custom Properties on `pf-search-input`" %}{% endrenderCssCustomProperties %} -{% renderCssParts for="pf-search-input", header="CSS Parts on `pf-search-input`" %}{% endrenderCssParts %} - -{% renderSlots for="pf-option", header="Slots on `pf-option`" %}{% endrenderSlots %} -{% renderAttributes for="pf-option", header="Attributes on `pf-option`" %}{% endrenderAttributes %} -{% renderMethods for="pf-option", header="Methods on `pf-option`" %}{% endrenderMethods %} -{% renderEvents for="pf-option", header="Events on `pf-option`" %}{% endrenderEvents %} -{% renderCssCustomProperties for="pf-option", header="CSS Custom Properties on `pf-option`" %}{% endrenderCssCustomProperties %} -{% renderCssParts for="pf-option", header="CSS Parts on `pf-option`" %}{% endrenderCssParts %} diff --git a/elements/pf-search-input/pf-search-input.css b/elements/pf-search-input/pf-search-input.css deleted file mode 100644 index 0e6bb54862..0000000000 --- a/elements/pf-search-input/pf-search-input.css +++ /dev/null @@ -1,308 +0,0 @@ -:host { - font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); - font-size: var(--pf-global--FontSize--md, 16px); - font-weight: var(--pf-global--FontWeight--normal, 400); - color: var(--pf-global--Color--100, #151515); - --_pf-option-checkboxes-display: none; - --_pf-option-svg-display: block; - --pf-c-search-input__toggle--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); - --pf-c-search-input__toggle--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-search-input__toggle--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); - --pf-c-search-input__toggle--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-search-input__toggle--MinWidth: var(--pf-global--target-size--MinWidth, 44px); - --pf-c-search-input__toggle--FontSize: var(--pf-global--FontSize--md, 1rem); - --pf-c-search-input__toggle--FontWeight: var(--pf-global--FontWeight--normal, 400); - --pf-c-search-input__toggle--LineHeight: var(--pf-global--LineHeight--md, 1.5); - --pf-c-search-input__toggle--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - --pf-c-search-input__toggle--before--BorderTopWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-search-input__toggle--before--BorderRightWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-search-input__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-search-input__toggle--before--BorderLeftWidth: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-search-input__toggle--before--BorderWidth: initial; - --pf-c-search-input__toggle--before--BorderTopColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-search-input__toggle--before--BorderRightColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-search-input__toggle--before--BorderBottomColor: var(--pf-global--BorderColor--200, #8a8d90); - --pf-c-search-input__toggle--before--BorderLeftColor: var(--pf-global--BorderColor--300, #f0f0f0); - --pf-c-search-input__toggle--Color: var(--pf-global--Color--100, #151515); - --pf-c-search-input__toggle--hover--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-search-input__toggle--focus--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-search-input__toggle--focus--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-search-input__toggle--active--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-search-input__toggle--active--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-search-input__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - --pf-c-search-input__toggle--m-expanded--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-search-input__toggle--disabled--BackgroundColor: var(--pf-global--disabled-color--300, #f0f0f0); - --pf-c-search-input__toggle--m-plain--before--BorderColor: transparent; - --pf-c-search-input__toggle--m-placeholder--Color: transparent; - --pf-c-search-input--m-invalid__toggle--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-search-input--m-invalid__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-search-input--m-invalid__toggle--hover--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-search-input--m-invalid__toggle--focus--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-search-input--m-invalid__toggle--active--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-search-input--m-invalid__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - --pf-c-search-input--m-invalid__toggle-status-icon--Color: var(--pf-global--danger-color--100, #c9190b); - --pf-c-search-input--m-success__toggle--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - --pf-c-search-input--m-success__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-search-input--m-success__toggle--hover--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - --pf-c-search-input--m-success__toggle--focus--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - --pf-c-search-input--m-success__toggle--active--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - --pf-c-search-input--m-success__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - --pf-c-search-input--m-success__toggle-status-icon--Color: var(--pf-global--success-color--100, #3e8635); - --pf-c-search-input--m-warning__toggle--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-search-input--m-warning__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - --pf-c-search-input--m-warning__toggle--hover--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-search-input--m-warning__toggle--focus--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-search-input--m-warning__toggle--active--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-search-input--m-warning__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-search-input--m-warning__toggle-status-icon--Color: var(--pf-global--warning-color--100, #f0ab00); - --pf-c-search-input__toggle-wrapper--not-last-child--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-search-input__toggle-wrapper--MaxWidth: calc(100% - var(--pf-global--spacer--lg, 1.5rem)); - --pf-c-search-input__toggle--m-placeholder__toggle-text--Color: var(--pf-global--Color--dark-200, #6a6e73); - --pf-c-search-input__toggle-icon--toggle-text--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-search-input__toggle-status-icon--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-search-input__toggle-status-icon--Color: var(--pf-global--Color--100, #151515); - --pf-c-search-input--m-plain__toggle-arrow--Color: var(--pf-global--Color--200, #6a6e73); - --pf-c-search-input--m-plain--hover__toggle-arrow--Color: var(--pf-global--Color--100, #151515); - --pf-c-search-input__toggle-clear--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-search-input__toggle-clear--PaddingLeft: var(--pf-global--spacer--md, 1rem); - --pf-c-search-input__toggle-clear--toggle-button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-search-input__toggle-button--Color: var(--pf-global--Color--100, #151515); - --pf-c-search-input__list-item--m-loading--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-search-input__menu-content--MaxHeight: 20rem; -} - -:host, -#outer { - display: flex; - flex-direction: column; - align-items: stretch; - inline-size: 100%; -} - -:host([hidden]), -*[hidden] { - display: none !important; -} - -:host([aria-disabled="true"]) { - pointer-events: none; - cursor: not-allowed; -} - -#outer.disabled { - color: var(--pf-global--Color--dark-200, #6a6e73); -} - -#outer { - position: relative; -} - -#listbox-container { - display: inline-flex; - position: absolute; - background-color: var(--pf-theme--color--surface--lightest, #fff) !important; - opacity: 0; - --_active-descendant-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - box-shadow: 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06); -} - -#outer.expanded #listbox-container { - opacity: 1; - z-index: 9999 !important; - max-block-size: var(--pf-c-search-input__menu-content--MaxHeight, 20rem); - overflow-y: scroll; -} - -#listbox { - display: flex; - flex-direction: column; - position: relative; - inline-size: 100%; -} - -#listbox slot.disabled { - color: var(--pf-c-list__item-icon--Color, #6a6e73) !important; - background-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - border-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - pointer-events: none; - cursor: not-allowed; - - --_active-descendant-color: transparent; - --_svg-color: var(--pf-c-list__item-icon--Color, #6a6e73) !important; -} - -#toggle { - background-color: var(--pf-c-search-input__toggle--BackgroundColor, #fff) !important; -} - -#toggle, -#toggle-input { - display: flex; - font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); - font-size: var(--pf-c-search-input__toggle--FontSize, 1rem); - font-weight: var(--pf-c-search-input__toggle--FontWeight, 400); - line-height: var(--pf-c-search-input__toggle--LineHeight, 1.5); -} - -#toggle { - border: 1px solid var(--pf-global--BorderColor--100, #d2d2d2); - border-bottom-color: var(--pf-theme--color--text, #151515); - justify-content: space-between; -} - -.disabled #toggle { - color: var(--pf-global--Color--dark-200, #6a6e73) !important; - background-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - border-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - caret-color: transparent; -} - -#toggle-input { - background: transparent; - border: none; - text-align: left; - border-radius: 0; - padding-inline-start: 3rem; -} - -#toggle-input { - justify-content: space-between; - inline-size: 100%; - box-sizing: border-box; - block-size: 2.25rem; -} - -.disabled #toggle-input { - pointer-events: none; - cursor: not-allowed; -} - -.close-button { - --pf-c-button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-button--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - --pf-c-button--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); - --pf-c-button--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); - - color: currentColor; - background-color: transparent; - max-block-size: 2.25rem; - max-inline-size: 2.25rem; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - border-radius: 0px; - position: relative; - - pf-icon { - position: relative; - inset-block-start: 5px; - } -} - -.close-button-container { - block-size: 2.25rem; - inline-size: 2.25rem; -} - -#toggle-text { - flex: 1 1 auto; -} - -#description { - display: block; -} - -#listbox.checkboxes { - --_pf-option-checkboxes-display: none; - --_pf-option-svg-display: none; -} - -.visually-hidden { - border: 0; - clip: rect(0, 0, 0, 0); - block-size: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - inline-size: 1px; -} - -::slotted(hr) { - --pf-c-divider--BorderWidth--base: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-divider--BorderColor--base: var(--pf-c-divider--BackgroundColor); - --pf-c-divider--Height: var(--pf-c-divider--BorderWidth--base); - --pf-c-divider--BackgroundColor: var(--pf-global--BorderColor--100, #d2d2d2); - --pf-c-divider--after--BackgroundColor: var(--pf-c-divider--BorderColor--base); - --pf-c-divider--after--FlexBasis: 100%; - --pf-c-divider--after--Inset: 0%; - --pf-c-divider--m-vertical--after--FlexBasis: 100%; - --pf-c-divider--m-horizontal--Display: flex; - --pf-c-divider--m-horizontal--FlexDirection: row; - --pf-c-divider--m-horizontal--after--Height: var(--pf-c-divider--Height); - --pf-c-divider--m-horizontal--after--Width: auto; - --pf-c-divider--m-vertical--Display: inline-flex; - --pf-c-divider--m-vertical--FlexDirection: column; - --pf-c-divider--m-vertical--after--Height: auto; - --pf-c-divider--m-vertical--after--Width: var(--pf-c-divider--BorderWidth--base); - --pf-hidden-visible--visible--Display: var(--pf-c-divider--Display); - --pf-c-divider--Display: var(--pf-c-divider--m-horizontal--Display); - --pf-c-divider--FlexDirection: var(--pf-c-divider--m-horizontal--FlexDirection); - --pf-c-divider--after--Width: var(--pf-c-divider--m-horizontal--after--Width); - --pf-c-divider--after--Height: var(--pf-c-divider--m-horizontal--after--Height); - display: var(--pf-c-divider--Display, flex); - flex-direction: var(--pf-c-divider--FlexDirection); - border: 0; - inline-size: 100%; - margin-top: var(--pf-c-search-input-menu--c-divider--MarginTop); - margin-bottom: var(--pf-c-search-input-menu--c-divider--MarginBottom); -} - -::slotted(hr)::after { - content: ''; - inline-size: var(--pf-c-divider--after--Width, 100%) !important; - block-size: var(--pf-c-divider--after--Height, 1px); - background-color: var(--pf-c-divider--after--BackgroundColor); - flex: 1 0 100%; -} - -div.search-icon { - position: absolute; - inset-block-start: 50%; - inset-inline-start: var(--pf-global--spacer--md, 1rem); - transform: translateY(-50%); - display: flex; - align-items: center; -} - -#outer:focus-within { - #toggle { - border-bottom: none; - border-bottom-left-radius: 4px; - - #toggle-input { - border-bottom: var(--pf-global--spacer--xs, 0.125rem) solid var(--pf-theme--color--accent, #0066cc); - } - } - - .close-button-container { - position: relative; - - &::after { - content: ''; - inline-size: 36px; - block-size: var(--pf-global--spacer--xs, 0.125rem); - inset-block-end: 0px; - inset-inline-start: 0px; - background-color: var(--pf-theme--color--accent, #0066cc); - position: absolute; - } - } -} - -::slotted(pf-option[selected]) { - --_pf-option-svg-display: none; - --_pf-option-selected-background-color: var(--pf-global--BackgroundColor--100, #fff); -} \ No newline at end of file diff --git a/elements/pf-search-input/pf-search-input.ts b/elements/pf-search-input/pf-search-input.ts deleted file mode 100644 index fe0fb9a192..0000000000 --- a/elements/pf-search-input/pf-search-input.ts +++ /dev/null @@ -1,339 +0,0 @@ -import type { Placement } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; -import type { TemplateResult } from 'lit'; - -import { LitElement, html, isServer } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { query } from 'lit/decorators/query.js'; -import { styleMap } from 'lit/directives/style-map.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { ComboboxController } from '@patternfly/pfe-core/controllers/combobox-controller.js'; -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; -import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; - -import { observes } from '@patternfly/pfe-core/decorators/observes.js'; -import { PfOption } from '../pf-select/pf-option.js'; -import styles from './pf-search-input.css'; - -/** Fired when a `` element's value changes */ -export class PfSearchChangeEvent extends Event { - constructor() { - super('change', { bubbles: true }); - } -} - -/** - * A search input lets users type in words to find specific items or information. - * As they type, it can show matching results to help them quickly find what they are looking for. - * - * A search input consists of a text field where users can type to find specific content or items. - * Unlike selects or dropdowns, which offer predefined options, a search input lets users enter - * their own keywords to filter or locate results. It includes a clear (×) button to easily - * remove the current input, allowing users to start a new search quickly. - * - * @summary Allows users to search through a list for specific search terms - * @alias Search Input - * - * @fires open - when the menu toggles open - * @fires close - when the menu toggles closed - */ -@customElement('pf-search-input') -export class PfSearchInput extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static readonly formAssociated = true; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** Accessible label for the search input */ - @property({ attribute: 'accessible-label' }) accessibleLabel?: string; - - /** Whether the search input is disabled */ - @property({ type: Boolean, reflect: true }) disabled = false; - - /** Whether the search input's listbox is expanded */ - @property({ type: Boolean, reflect: true }) expanded = false; - - /** Current form value */ - @property() value?: string; - - /** Placeholder entry. Overridden by the `placeholder` slot */ - @property() placeholder?: string; - - /** - * Indicates initial popover position. - * There are 6 options: `bottom`, `top`, `top-start`, `top-end`, `bottom-start`, `bottom-end`. - * Default is `bottom`. - */ - @property({ reflect: true }) position: Placement = 'bottom'; - - @query('#toggle-input') private _toggleInput?: HTMLInputElement; - - @query('#toggle-button') private _toggleButton?: HTMLDivElement; - - @query('#listbox') private _listbox?: HTMLElement; - - @query('#listbox-container') private _listboxContainer?: HTMLElement; - - @query('#placeholder') private _placeholder?: PfOption; - - #internals = InternalsController.of(this); - - #float = new FloatingDOMController(this, { content: () => this._listboxContainer }); - - #slots = new SlotController(this, null, 'placeholder'); - - /** True when the user just clicked the close button */ - #clickedCloseButton = false; - #setExpanded = false; - - #combobox = ComboboxController.of(this, { - getItems: () => this.options, - getFallbackLabel: () => this.accessibleLabel - || this.#internals.computedLabelText - || this.placeholder - || this.#slots.getSlotted('placeholder').map(x => x.textContent).join(''), - getListboxElement: () => this._listbox ?? null, - getToggleButton: () => this._toggleButton ?? null, - getComboboxInput: () => this._toggleInput ?? null, - isExpanded: () => this.#setIsExpanded(), - requestShowListbox: () => this.#showListbox(), - requestHideListbox: () => void (this.expanded &&= false), - setItemHidden: (item, hidden) => (item.id !== 'placeholder') && void (item.hidden = hidden), - isItem: item => item instanceof PfOption, - setItemActive: (item, active) => this.#setItemActive(item, active), - setItemSelected: (item, selected) => this.#setItemSelected(item, selected), - }); - - connectedCallback(): void { - super.connectedCallback(); - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - } - - /** List of options */ - get options(): PfOption[] { - if (isServer) { - return []; // TODO: expose a DOM property to allow setting options in SSR scenarios - } else { - return [ - this._placeholder, - ...Array.from(this.querySelectorAll('pf-option')), - ].filter((x): x is PfOption => !!x && !x.hidden); - } - } - - override render(): TemplateResult<1> { - const { disabled, expanded, placeholder } = this; - const { anchor = 'bottom', alignment = 'start', styles = {} } = this.#float; - const { height, width } = this.getBoundingClientRect?.() || {}; - - return html` -
-
-
- search -
- -
- - close - -
-
-
-
- ${this.#combobox.renderItemsToShadowRoot()} - - -
-
-
- `; - } - - @observes('disabled') - private disabledChanged() { - this.#combobox.disabled = this.disabled; - } - - @observes('expanded') - private async expandedChanged(old: boolean, expanded: boolean) { - if (this.dispatchEvent(new Event(this.expanded ? 'close' : 'open'))) { - if (expanded) { - this.#doExpand(); - } else { - this.#doCollapse(); - } - } - } - - @observes('value') - private valueChanged() { - this.#internals.setFormValue(this.value ?? ''); - this.dispatchEvent(new PfSearchChangeEvent()); - } - - async #doExpand() { - try { - await this.#float.show({ placement: this.position || 'bottom', flip: true }); - return true; - } catch { - return false; - } - } - - async #doCollapse() { - try { - await this.#float.hide(); - return true; - } catch { - return false; - } - } - - /** - * Opens the dropdown - */ - async show(): Promise { - this.expanded = true; - await this.updateComplete; - } - - /** - * Closes listbox - */ - async hide(): Promise { - this.expanded = false; - await this.updateComplete; - } - - /** - * toggles popup based on current state - */ - async toggle(): Promise { - if (this.expanded) { - await this.hide(); - } else { - await this.show(); - } - } - - #onClickCloseButton() { - this._toggleInput!.value = ''; - this.#updateValue(this._toggleInput?.value ?? ''); - this.#clickedCloseButton = true; - this._toggleInput?.focus(); - } - - #hideCloseButton() { - if (!isServer) { - return !this.expanded && this._toggleInput?.value.trim() === ''; // SSR or server-side environment: don't hide the element - } - return false; - } - - #onChange() { - this.#updateValue(this._toggleInput?.value ?? ''); - } - - #onSubmit(event: KeyboardEvent) { - if (event.key === 'Enter' || event.key === ' ') { - this.dispatchEvent(new PfSearchChangeEvent()); - } - } - - #updateValue(value: string) { - this.value = value; - // it's necessary to reset the 'selected' state of combobox - // since otherwise, combobox controller will attempt to prevent us from - // re-selecting the last-selected item, even though pf-search-input - // doesn't have a selected property - this.#combobox.selected = []; - } - - #onKeyDown(event: KeyboardEvent) { - const target = event.target as HTMLElement; - if (target?.getAttribute('aria-disabled') === 'true') { - // Allow Tab and Shift+Tab to move focus - if (event.key === 'Tab') { - return; - } - event.preventDefault(); - event.stopImmediatePropagation(); - } - } - - async #showListbox() { - await new Promise(requestAnimationFrame); - if (this.disabled) { - return; - }; - - if (this.#setExpanded) { - // If expanded is set to true on clicking close button - // set expanded to false - this.#setExpanded = false; - this.expanded = false; - } else { - this.expanded ||= true; - } - } - - #setItemSelected(item: PfOption, selected: boolean) { - item.selected = selected; - if (selected) { - this._toggleInput!.value = item.value; - this.#updateValue(this._toggleInput?.value ?? ''); - } - } - - #setItemActive(item: PfOption, active: boolean) { - item.active = active; - if (this.expanded && active) { - item?.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); - } - } - - #setIsExpanded() { - if (this.#clickedCloseButton) { - this.#clickedCloseButton = false; - this.#setExpanded = true; - return true; - } - return this.expanded; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-search-input': PfSearchInput; - } -} diff --git a/elements/pf-select/README.md b/elements/pf-select/README.md deleted file mode 100644 index 1451de32b0..0000000000 --- a/elements/pf-select/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Select - -A select list enables users to select one or more items from a list. - -## Usage - -A select component consists of a toggle control to open and close a menu of actions or links. -Selects differ from dropdowns in that they persist selection, whereas dropdowns are typically used to present a list of actions or links. - -```html - - - Blue - Green - Magenta - Orange - Purple - Pink - Red - Yellow - -``` diff --git a/elements/pf-select/demo/checkbox-input-no-badge.html b/elements/pf-select/demo/checkbox-input-no-badge.html deleted file mode 100644 index f1561396fc..0000000000 --- a/elements/pf-select/demo/checkbox-input-no-badge.html +++ /dev/null @@ -1,24 +0,0 @@ -
- - Debug - Info - Warn - Error - -
- - - - diff --git a/elements/pf-select/demo/checkbox-input.html b/elements/pf-select/demo/checkbox-input.html deleted file mode 100644 index cd7003192b..0000000000 --- a/elements/pf-select/demo/checkbox-input.html +++ /dev/null @@ -1,23 +0,0 @@ -
- - Active - Cancelled - Paused -
- Warning - Restarted -
-
- - - - diff --git a/elements/pf-select/demo/grouped-checkbox-input.html b/elements/pf-select/demo/grouped-checkbox-input.html deleted file mode 100644 index 2cf471d318..0000000000 --- a/elements/pf-select/demo/grouped-checkbox-input.html +++ /dev/null @@ -1,31 +0,0 @@ -
- - - Running - Stopped - Down - Degraded - Needs maintenance - -
- - Dell - Samsung - Hewlett-Packard - -
-
- - - - - diff --git a/elements/pf-select/demo/grouped-single.html b/elements/pf-select/demo/grouped-single.html deleted file mode 100644 index 727c547764..0000000000 --- a/elements/pf-select/demo/grouped-single.html +++ /dev/null @@ -1,30 +0,0 @@ -
- - - Running - Stopped - Down - Degraded - Needs maintenance - -
- - Dell - Samsung - Hewlett-Packard - -
-
- - - - diff --git a/elements/pf-select/demo/index.html b/elements/pf-select/demo/index.html deleted file mode 100644 index b279eaeb79..0000000000 --- a/elements/pf-select/demo/index.html +++ /dev/null @@ -1,26 +0,0 @@ -
- - Mr - Miss - Mrs - Ms -
- Dr - Other -
-
- - - - - diff --git a/elements/pf-select/demo/single-with-description.html b/elements/pf-select/demo/single-with-description.html deleted file mode 100644 index a757de8bf3..0000000000 --- a/elements/pf-select/demo/single-with-description.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - Mr - Miss - Mrs - Ms - Dr - Descriptions can also be HTML - - Other - -
- - - - diff --git a/elements/pf-select/demo/typeahead.html b/elements/pf-select/demo/typeahead.html deleted file mode 100644 index 303fa43faa..0000000000 --- a/elements/pf-select/demo/typeahead.html +++ /dev/null @@ -1,29 +0,0 @@ -
- - - - - - - - - -
- - - - diff --git a/elements/pf-select/demos-to-implement-later/multiple.html b/elements/pf-select/demos-to-implement-later/multiple.html deleted file mode 100644 index 72de91f3f9..0000000000 --- a/elements/pf-select/demos-to-implement-later/multiple.html +++ /dev/null @@ -1,19 +0,0 @@ - - Blue - Green - Magenta - Orange - Purple - Pink - Red - Yellow - - -

- Shift will toggling off multiple items. - Ctrl+A will toggle selection on all items. -

- - diff --git a/elements/pf-select/demos-to-implement-later/typeahead-chips.html b/elements/pf-select/demos-to-implement-later/typeahead-chips.html deleted file mode 100644 index 2ba13235ad..0000000000 --- a/elements/pf-select/demos-to-implement-later/typeahead-chips.html +++ /dev/null @@ -1,17 +0,0 @@ - - - Blue - Green - Magenta - Orange - Purple - Pink - Red - Yellow - - - diff --git a/elements/pf-select/demos-to-implement-later/typeahead-create-option.html b/elements/pf-select/demos-to-implement-later/typeahead-create-option.html deleted file mode 100644 index 5c676d44a4..0000000000 --- a/elements/pf-select/demos-to-implement-later/typeahead-create-option.html +++ /dev/null @@ -1,17 +0,0 @@ - - - Blue - Green - Magenta - Orange - Purple - Pink - Red - Yellow - - - diff --git a/elements/pf-select/demos-to-implement-later/typeahead-custom-filter.html b/elements/pf-select/demos-to-implement-later/typeahead-custom-filter.html deleted file mode 100644 index e4393b6a8a..0000000000 --- a/elements/pf-select/demos-to-implement-later/typeahead-custom-filter.html +++ /dev/null @@ -1,18 +0,0 @@ - - Blue - Green - Magenta - Orange - Purple - Pink - Red - Yellow - - - diff --git a/elements/pf-select/demos-to-implement-later/typeahead-disable-filter.html b/elements/pf-select/demos-to-implement-later/typeahead-disable-filter.html deleted file mode 100644 index 66683f85f5..0000000000 --- a/elements/pf-select/demos-to-implement-later/typeahead-disable-filter.html +++ /dev/null @@ -1,15 +0,0 @@ - - - Blue - Green - Magenta - Orange - Purple - Pink - Red - Yellow - - - diff --git a/elements/pf-select/demos-to-implement-later/typeahead-multiple.html b/elements/pf-select/demos-to-implement-later/typeahead-multiple.html deleted file mode 100644 index 45fe5d6d08..0000000000 --- a/elements/pf-select/demos-to-implement-later/typeahead-multiple.html +++ /dev/null @@ -1,15 +0,0 @@ - - - diff --git a/elements/pf-select/docs/pf-select.md b/elements/pf-select/docs/pf-select.md deleted file mode 100644 index e784e783bd..0000000000 --- a/elements/pf-select/docs/pf-select.md +++ /dev/null @@ -1,164 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - - - -{% renderOverview %} - - Blue - Green - Magenta - Orange - Purple - Pink - Red - Yellow - -{% endrenderOverview %} - -{% band header="Usage" %} - -#### Single - -Focus on options using arrow keys or -by typing the first character of an option. - -{% htmlexample %} - {% renderFile "./elements/pf-select/demo/index.html" %} -{% endhtmlexample %} - -#### Single with description -{% htmlexample %} - {% renderFile "./elements/pf-select/demo/single-with-description.html" %} -{% endhtmlexample %} - -#### Grouped single -{% htmlexample %} - {% renderFile "./elements/pf-select/demo/grouped-single.html" %} -{% endhtmlexample %} - -#### Checkbox input - -Multiple options can be selected. Any arrow keys work. -Shift will toggling off multiple items. -Ctrl+A will toggle selection on all items. - -{% htmlexample %} - {% renderFile "./elements/pf-select/demo/checkbox-input.html" %} -{% endhtmlexample %} - -{# save this for v5 -### Option variations - -Below are option variants: - -{% htmlexample %} - - Basic option - - Option with description - This is a description - - - - Option with icon - - Aria-disabled option - -{% endhtmlexample %} -#} -When setting the `disabled` attribute on options, they are still focusable, but -they are not selectable. This is in order that they remain accessible to screen -readers. This functions similarly to the `aria-disabled="true"` attribute. - -{% renderFile "./docs/_snippets/wai-aria-disabled.md" %} - - ### Typeahead - {% htmlexample %} - {% renderFile "./elements/pf-select/demo/typeahead.html" %} - {% endhtmlexample %} - -{# - - #### Multiple - - {% htmlexample %} - {% renderFile "./elements/pf-select/demo/typeahead-multiple.html" %} - {% endhtmlexample %} - - #### Custom filtering - - By default, filtering is **enabled** and **not** case sensitive. - However, filtering can be customized with the `customFilter` option, - which is a predicate function that takes an option. - - {% htmlexample %} - {% renderFile "./elements/pf-select/demo/typeahead-custom-filter.html" %} - {% endhtmlexample %} -#} - -{% endband %} - -{% band header="Accessibility" %} - -The select uses the [Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) recommendations from the WAI ARIA [Authoring Best Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg). - -When the dropdown is disabled it follows [WAI ARIA focusability recommendations](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols) for composite widget elements, where dropdown items are still focusable even when the dropdown is disabled. - -#### Toggle button and typeahead input - -When focus is on the toggle button, the following keyboard interactions apply: - -| Key | Function | -| ---------------------- | -------------------------------------------------------------------------------------- | -| Enter | Opens the listbox. | -| Space | Opens the listbox. | -| Down Arrow | Opens the listbox and moves focus to the first listbox item. | -| Tab | Moves focus out of select element onto the next focusable item and closes listbox. | -| Shift + Tab | Moves focus out of select element onto the previous focusable item and closes listbox. | - -#### Listbox options - -Listbox options use the [APG's Roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) recommendation. When focus is on the listbox, the following keyboard interactions apply: - -| Key | Function | -| ---------------------- | ------------------------------------------------------------------------------------- | -| Enter | Selects the options and closes the listbox. | -| Space | Selects the options and closes the listbox. | -| Shift | Enables multiselect. | -| Control + A | Selects all options. | -| Tab | Moves focus out of select element onto the next focusable options and closes listbox. | -| Shift + Tab | Moves focus to the toggle button and closes listbox. | -| Up Arrow | Moves focus to the previous option, optionally wrapping from the first to the last. | -| Down Arrow | Moves focus to the next option, optionally wrapping from the last to the first. | -| Left Arrow | Moves focus to the previous option, optionally wrapping from the first to the last. | -| Right Arrow | Moves focus to the next option, optionally wrapping from the last to the first. | -| Home | Moves focus to the first option in the current listbox. | -| End | Moves focus to the last option in the current listbox. | -| Escape | Close the listbox that contains focus and return focus to the toggle button. | -| Any letter | Navigates to the next option that starts with the letter. | - -{% endband %} - -{% renderSlots for="pf-select", header="Slots on `pf-select`" %}{% endrenderSlots %} -{% renderAttributes for="pf-select", header="Attributes on `pf-select`" %}{% endrenderAttributes %} -{% renderMethods for="pf-select", header="Methods on `pf-select`" %}{% endrenderMethods %} -{% renderEvents for="pf-select", header="Events on `pf-select`" %}{% endrenderEvents %} -{% renderCssCustomProperties for="pf-select", header="CSS Custom Properties on `pf-select`" %}{% endrenderCssCustomProperties %} -{% renderCssParts for="pf-select", header="CSS Parts on `pf-select`" %}{% endrenderCssParts %} - -{% renderSlots for="pf-option-group", header="Slots on `pf-option-group`" %}{% endrenderSlots %} -{% renderAttributes for="pf-option-group", header="Attributes on `pf-option-group`" %}{% endrenderAttributes %} -{% renderMethods for="pf-option-group", header="Methods on `pf-option-group`" %}{% endrenderMethods %} -{% renderEvents for="pf-option-group", header="Events on `pf-option-group`" %}{% endrenderEvents %} -{% renderCssCustomProperties for="pf-option-group", header="CSS Custom Properties on `pf-option-group`" %}{% endrenderCssCustomProperties %} -{% renderCssParts for="pf-option-group", header="CSS Parts on `pf-option-group`" %}{% endrenderCssParts %} - -{% renderSlots for="pf-option", header="Slots on `pf-option`" %}{% endrenderSlots %} -{% renderAttributes for="pf-option", header="Attributes on `pf-option`" %}{% endrenderAttributes %} -{% renderMethods for="pf-option", header="Methods on `pf-option`" %}{% endrenderMethods %} -{% renderEvents for="pf-option", header="Events on `pf-option`" %}{% endrenderEvents %} -{% renderCssCustomProperties for="pf-option", header="CSS Custom Properties on `pf-option`" %}{% endrenderCssCustomProperties %} -{% renderCssParts for="pf-option", header="CSS Parts on `pf-option`" %}{% endrenderCssParts %} diff --git a/elements/pf-select/pf-option-group.css b/elements/pf-select/pf-option-group.css deleted file mode 100644 index cf4b47c5ef..0000000000 --- a/elements/pf-select/pf-option-group.css +++ /dev/null @@ -1,25 +0,0 @@ -:host { - display: block; - border-bottom: 1px solid var(--pf-global--BorderColor--100, #d2d2d2); -} - -:host([disabled]) { - pointer-events: none; - cursor: not-allowed; - color: var(--pf-global--Color--200, #6a6e73) !important; - background-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - border-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - --_active-descendant-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - --_svg-color: var(--pf-global--Color--200, #6a6e73) !important; -} - -slot { - display: block; - padding: var(--pf-global--spacer--md, 1rem) 0; -} - -slot[name="label"] { - font-size: var(--pf-global--FontSize--xs, 0.75rem); - color: var(--pf-global--Color--dark-200, #6a6e73); - padding: var(--pf-global--spacer--md, 1rem) var(--pf-global--spacer--md, 1rem) 0; -} diff --git a/elements/pf-select/pf-option-group.ts b/elements/pf-select/pf-option-group.ts deleted file mode 100644 index 8c6be3cc22..0000000000 --- a/elements/pf-select/pf-option-group.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { LitElement, html, type TemplateResult } 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 { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import styles from './pf-option-group.css'; - -/** - * Group of options within a listbox - * @slot - `` or `
` elements - * @slot label - Group label. Overrides the `label` attribute. - */ -@customElement('pf-option-group') -export class PfOptionGroup extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** Group description. Overridden by `label` slot. */ - @property() label?: string; - - /** whether group is disabled */ - @property({ type: Boolean, reflect: true }) disabled = false; - - // for the role - // eslint-disable-next-line no-unused-private-class-members - #internals = InternalsController.of(this, { role: 'group' }); - - render(): TemplateResult<1> { - const { disabled } = this; - return html` - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-option-group': PfOptionGroup; - } -} diff --git a/elements/pf-select/pf-option.css b/elements/pf-select/pf-option.css deleted file mode 100644 index 10dfe0da21..0000000000 --- a/elements/pf-select/pf-option.css +++ /dev/null @@ -1,82 +0,0 @@ -:host { - display: block; -} - -:host([hidden]), -*[hidden] { - display: none !important; -} - -:host([disabled]) { - pointer-events: none !important; - cursor: not-allowed !important; -} - -:host(:focus) #outer, -:host(:hover) #outer, -#outer.selected { - background-color: var(--_pf-option-selected-background-color, var(--pf-global--BackgroundColor--200, #e0e0e0)); -} - -#outer { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: flex-start; - padding: var(--pf-global--spacer--sm, 0.5rem) var(--pf-global--spacer--md, 1rem); - min-height: calc(44px - 2 * var(--pf-global--spacer--sm, 0.5rem)); - min-width: calc(44px - 2 * var(--pf-global--spacer--md, 1rem)); -} - -#outer.active { - background-color: var(--_active-descendant-color, var(--pf-theme--color--surface--lighter, #f0f0f0)); -} - -:host([disabled]) #outer { - color: var(--pf-global--Color--dark-200, #6a6e73) !important; -} - -input[type="checkbox"] { - margin-inline-end: 1em; - display: var(--_pf-option-checkboxes-display, none); - pointer-events: none; - flex: 0 0 auto; -} - -span { - flex: 1 1 auto; -} - -svg { - font-size: var(--pf-c-select__menu-item-icon--FontSize, var(--pf-global--icon--FontSize--sm, 0.675rem)); - color: var(--_svg-color, var(--pf-theme--color--accent, #0066cc)); - width: 1em; - height: 1em; - margin-inline-start: 1em; - text-align: right; - flex: 0 0 auto; - display: var(--_pf-option-svg-display, block); -} - -#description { - display: block; - flex: 1 0 100%; -} - -slot[name="description"] { - font-size: var(--pf-global--FontSize--xs, 0.75rem); - color: var(--pf-global--Color--dark-200, #6a6e73); -} - -::slotted([slot="icon"]) { - margin-inline-end: 0.5em; -} - -:host([suggestion]) { - #outer.selected { - background-color: transparent; - } - svg { - display: none; - } -} \ No newline at end of file diff --git a/elements/pf-select/pf-option.ts b/elements/pf-select/pf-option.ts deleted file mode 100644 index e36bd2aca0..0000000000 --- a/elements/pf-select/pf-option.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { queryAssignedNodes } from 'lit/decorators/query-assigned-nodes.js'; -import { property } from 'lit/decorators/property.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import { observes } from '@patternfly/pfe-core/decorators/observes.js'; - -import styles from './pf-option.css'; - -/** - * Option within a listbox - * @slot - - * option text - * @slot icon - * optional icon - * @slot description - * optional description - */ -@customElement('pf-option') -export class PfOption extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - /** whether option is disabled */ - @property({ type: Boolean, reflect: true }) disabled = false; - - /** form value for this option */ - @property({ reflect: true }) - get value() { - return (this.#value ?? this.textContent ?? '').trim(); - } - - set value(v: string) { - this.#value = v; - } - - /** whether option is selected */ - @property({ type: Boolean, reflect: true }) selected = false; - - /** whether option is active descendant */ - @property({ type: Boolean, reflect: true }) active = false; - - /** Optional option description; overridden by description slot. */ - @property() description = ''; - - @queryAssignedNodes({ slot: '', flatten: true }) - private _slottedText!: Node[]; - - /** - * this option's position relative to the other options - */ - set posInSet(posInSet: number | null) { - this.#internals.ariaPosInSet = `${Math.max(0, posInSet ?? 0)}`; - } - - get posInSet() { - const parsed = parseInt(this.#internals.ariaPosInSet ?? '0'); - return Number.isNaN(parsed) ? null : parsed; - } - - /** - * total number of options - */ - set setSize(setSize: number | null) { - this.#internals.ariaSetSize = `${Math.max(0, setSize ?? 0)}`; - } - - get setSize() { - try { - const int = parseInt(this.#internals.ariaSetSize ?? '0'); - if (Number.isNaN(int)) { - return 0; - } else { - return int; - } - } catch { - return 0; - } - } - - #value?: string; - - #internals = InternalsController.of(this, { role: 'option' }); - - render(): TemplateResult<1> { - const { disabled, active, selected } = this; - return html` -
- - - - - ${this.value} - - - ${this.description ?? ''} -
- `; - } - - @observes('selected') - private selectedChanged() { - this.#internals.ariaSelected = String(!!this.selected); - } - - @observes('disabled') - private disabledChanged() { - this.#internals.ariaDisabled = String(!!this.disabled); - } - - /** - * text content within option (used for filtering) - */ - get optionText(): string { - return this._slottedText.map(node => node.textContent).join('').trim(); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-option': PfOption; - } -} diff --git a/elements/pf-select/pf-select.css b/elements/pf-select/pf-select.css deleted file mode 100644 index 4192cb7175..0000000000 --- a/elements/pf-select/pf-select.css +++ /dev/null @@ -1,536 +0,0 @@ -:host { - font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); - font-size: var(--pf-global--FontSize--md, 16px); - font-weight: var(--pf-global--FontWeight--normal, 400); - color: var(--pf-global--Color--100, #151515); - --_pf-option-checkboxes-display: none; - --_pf-option-svg-display: block; - --_pf-option-selected-background-color: var(--pf-global--BackgroundColor--200, #e0e0e0); - /** Select toggle top padding */ - --pf-c-select__toggle--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); - /** Select toggle right padding */ - --pf-c-select__toggle--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - /** Select toggle bottom padding */ - --pf-c-select__toggle--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); - /** Select toggle left padding */ - --pf-c-select__toggle--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - /** Select toggle minimum width */ - --pf-c-select__toggle--MinWidth: var(--pf-global--target-size--MinWidth, 44px); - /** Select toggle font size */ - --pf-c-select__toggle--FontSize: var(--pf-global--FontSize--md, 1rem); - /** Select toggle font weight */ - --pf-c-select__toggle--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** Select toggle line height */ - --pf-c-select__toggle--LineHeight: var(--pf-global--LineHeight--md, 1.5); - /** Select toggle background color */ - --pf-c-select__toggle--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); - /** Select toggle border top width */ - --pf-c-select__toggle--before--BorderTopWidth: var(--pf-global--BorderWidth--sm, 1px); - /** Select toggle border right width */ - --pf-c-select__toggle--before--BorderRightWidth: var(--pf-global--BorderWidth--sm, 1px); - /** Select toggle border bottom width */ - --pf-c-select__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--sm, 1px); - /** Select toggle border left width */ - --pf-c-select__toggle--before--BorderLeftWidth: var(--pf-global--BorderWidth--sm, 1px); - /** Select toggle border width */ - --pf-c-select__toggle--before--BorderWidth: initial; - /** Select toggle border top color */ - --pf-c-select__toggle--before--BorderTopColor: var(--pf-global--BorderColor--300, #f0f0f0); - /** Select toggle border right color */ - --pf-c-select__toggle--before--BorderRightColor: var(--pf-global--BorderColor--300, #f0f0f0); - /** Select toggle border bottom color */ - --pf-c-select__toggle--before--BorderBottomColor: var(--pf-global--BorderColor--200, #8a8d90); - /** Select toggle border left color */ - --pf-c-select__toggle--before--BorderLeftColor: var(--pf-global--BorderColor--300, #f0f0f0); - /** Select toggle text color */ - --pf-c-select__toggle--Color: var(--pf-global--Color--100, #151515); - /** Select toggle hover border bottom color */ - --pf-c-select__toggle--hover--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - /** Select toggle focus border bottom color */ - --pf-c-select__toggle--focus--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - /** Select toggle focus border bottom width */ - --pf-c-select__toggle--focus--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - /** Select toggle active border bottom color */ - --pf-c-select__toggle--active--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - /** Select toggle active border bottom width */ - --pf-c-select__toggle--active--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - /** Select toggle expanded border bottom color */ - --pf-c-select__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); - /** Select toggle expanded border bottom width */ - --pf-c-select__toggle--m-expanded--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - /** Select toggle disabled background color */ - --pf-c-select__toggle--disabled--BackgroundColor: var(--pf-global--disabled-color--300, #f0f0f0); - /** Select toggle plain border color */ - --pf-c-select__toggle--m-plain--before--BorderColor: transparent; - /** Select toggle placeholder color */ - --pf-c-select__toggle--m-placeholder--Color: transparent; - /** Select invalid state toggle border bottom color */ - --pf-c-select--m-invalid__toggle--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - /** Select invalid state toggle border bottom width */ - --pf-c-select--m-invalid__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - /** Select invalid state toggle hover border bottom color */ - --pf-c-select--m-invalid__toggle--hover--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - /** Select invalid state toggle focus border bottom color */ - --pf-c-select--m-invalid__toggle--focus--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - /** Select invalid state toggle active border bottom color */ - --pf-c-select--m-invalid__toggle--active--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - /** Select invalid state toggle expanded border bottom color */ - --pf-c-select--m-invalid__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); - /** Select invalid state toggle status icon color */ - --pf-c-select--m-invalid__toggle-status-icon--Color: var(--pf-global--danger-color--100, #c9190b); - /** Select success state toggle border bottom color */ - --pf-c-select--m-success__toggle--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - /** Select success state toggle border bottom width */ - --pf-c-select--m-success__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - /** Select success state toggle hover border bottom color */ - --pf-c-select--m-success__toggle--hover--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - /** Select success state toggle focus border bottom color */ - --pf-c-select--m-success__toggle--focus--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - /** Select success state toggle active border bottom color */ - --pf-c-select--m-success__toggle--active--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - /** Select success state toggle expanded border bottom color */ - --pf-c-select--m-success__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); - /** Select success state toggle status icon color */ - --pf-c-select--m-success__toggle-status-icon--Color: var(--pf-global--success-color--100, #3e8635); - /** Select warning state toggle border bottom color */ - --pf-c-select--m-warning__toggle--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - /** Select warning state toggle border bottom width */ - --pf-c-select--m-warning__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); - /** Select warning state toggle hover border bottom color */ - --pf-c-select--m-warning__toggle--hover--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - /** Select warning state toggle focus border bottom color */ - --pf-c-select--m-warning__toggle--focus--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - /** Select warning state toggle active border bottom color */ - --pf-c-select--m-warning__toggle--active--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - /** Select warning state toggle expanded border bottom color */ - --pf-c-select--m-warning__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); - /** Select warning state toggle status icon color */ - --pf-c-select--m-warning__toggle-status-icon--Color: var(--pf-global--warning-color--100, #f0ab00); - /** Select toggle wrapper not last child right margin */ - --pf-c-select__toggle-wrapper--not-last-child--MarginRight: var(--pf-global--spacer--xs, 0.25rem); - /** Select toggle wrapper maximum width */ - --pf-c-select__toggle-wrapper--MaxWidth: calc(100% - var(--pf-global--spacer--lg, 1.5rem)); - /** Select toggle wrapper chip group top margin */ - --pf-c-select__toggle-wrapper--c-chip-group--MarginTop: 0.3125rem; - /** Select toggle wrapper chip group bottom margin */ - --pf-c-select__toggle-wrapper--c-chip-group--MarginBottom: 0.3125rem; - /** Select typeahead toggle flex basis */ - --pf-c-select__toggle-typeahead--FlexBasis: 10em; - /** Select typeahead toggle background color */ - --pf-c-select__toggle-typeahead--BackgroundColor: transparent; - /** Select typeahead toggle top border */ - --pf-c-select__toggle-typeahead--BorderTop: var(--pf-global--BorderWidth--sm, 1px) solid transparent; - /** Select typeahead toggle right border */ - --pf-c-select__toggle-typeahead--BorderRight: none; - /** Select typeahead toggle left border */ - --pf-c-select__toggle-typeahead--BorderLeft: none; - /** Select typeahead toggle minimum width */ - --pf-c-select__toggle-typeahead--MinWidth: 7.5rem; - /** Select typeahead toggle focus bottom padding */ - --pf-c-select__toggle-typeahead--focus--PaddingBottom: calc(var(--pf-global--spacer--form-element, 0.375rem) - var(--pf-global--BorderWidth--md)); - /** Select toggle placeholder text color */ - --pf-c-select__toggle--m-placeholder__toggle-text--Color: var(--pf-global--Color--dark-200, #6a6e73); - /** Select toggle icon left margin */ - --pf-c-select__toggle-icon--toggle-text--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - /** Select toggle badge left padding */ - --pf-c-select__toggle-badge--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - /** Select toggle status icon left margin */ - --pf-c-select__toggle-status-icon--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); - /** Select toggle status icon color */ - --pf-c-select__toggle-status-icon--Color: var(--pf-global--Color--100, #151515); - /** Select toggle arrow left margin */ - --pf-c-select__toggle-arrow--MarginLeft: var(--pf-global--spacer--md, 1rem); - /** Select toggle arrow right margin */ - --pf-c-select__toggle-arrow--MarginRight: var(--pf-global--spacer--sm, 0.5rem); - /** Select toggle arrow with clear left margin */ - --pf-c-select__toggle-arrow--with-clear--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); - /** Select toggle arrow top expanded rotation */ - --pf-c-select__toggle-arrow--m-top--m-expanded__toggle-arrow--Rotate: 180deg; - /** Select plain toggle arrow color */ - --pf-c-select--m-plain__toggle-arrow--Color: var(--pf-global--Color--200, #6a6e73); - /** Select plain hover toggle arrow color */ - --pf-c-select--m-plain--hover__toggle-arrow--Color: var(--pf-global--Color--100, #151515); - /** Select toggle clear right padding */ - --pf-c-select__toggle-clear--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); - /** Select toggle clear left padding */ - --pf-c-select__toggle-clear--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** Select toggle clear button left padding */ - --pf-c-select__toggle-clear--toggle-button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); - /** Select toggle button text color */ - --pf-c-select__toggle-button--Color: var(--pf-global--Color--100, #151515); - /** Select menu background color */ - --pf-c-select__menu--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #fff); - /** Select menu box shadow */ - --pf-c-select__menu--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); - /** Select menu top padding */ - --pf-c-select__menu--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu bottom padding */ - --pf-c-select__menu--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu top position */ - --pf-c-select__menu--Top: calc(100% + var(--pf-global--spacer--xs, 0.25rem)); - /** Select menu z-index */ - --pf-c-select__menu--ZIndex: var(--pf-global--ZIndex--sm, 200); - /** Select menu width */ - --pf-c-select__menu--Width: auto; - /** Select menu minimum width */ - --pf-c-select__menu--MinWidth: 100%; - /** Select menu top transform Y */ - --pf-c-select__menu--m-top--TranslateY: calc(-100% - var(--pf-global--spacer--xs, 0.25rem)); - /** Select list item loading top padding */ - --pf-c-select__list-item--m-loading--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu item top padding */ - --pf-c-select__menu-item--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu item right padding */ - --pf-c-select__menu-item--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** Select menu item selected right padding */ - --pf-c-select__menu-item--m-selected--PaddingRight: var(--pf-global--spacer--2xl, 3rem); - /** Select menu item bottom padding */ - --pf-c-select__menu-item--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu item left padding */ - --pf-c-select__menu-item--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** Select menu item font size */ - --pf-c-select__menu-item--FontSize: var(--pf-global--FontSize--md, 1rem); - /** Select menu item font weight */ - --pf-c-select__menu-item--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** Select menu item line height */ - --pf-c-select__menu-item--LineHeight: var(--pf-global--LineHeight--md, 1.5); - /** Select menu item text color */ - --pf-c-select__menu-item--Color: var(--pf-global--Color--dark-100, #151515); - /** Select menu item disabled text color */ - --pf-c-select__menu-item--disabled--Color: var(--pf-global--Color--dark-200, #6a6e73); - /** Select menu item width */ - --pf-c-select__menu-item--Width: 100%; - /** Select menu item hover background color */ - --pf-c-select__menu-item--hover--BackgroundColor: var(--pf-global--BackgroundColor--light-300, #f0f0f0); - /** Select menu item focus background color */ - --pf-c-select__menu-item--focus--BackgroundColor: var(--pf-global--BackgroundColor--light-300, #f0f0f0); - /** Select menu item disabled background color */ - --pf-c-select__menu-item--disabled--BackgroundColor: transparent; - /** Select menu item link width */ - --pf-c-select__menu-item--m-link--Width: auto; - /** Select menu item link hover background color */ - --pf-c-select__menu-item--m-link--hover--BackgroundColor: transparent; - /** Select menu item link focus background color */ - --pf-c-select__menu-item--m-link--focus--BackgroundColor: transparent; - /** Select menu item action color */ - --pf-c-select__menu-item--m-action--Color: var(--pf-global--Color--200, #6a6e73); - /** Select menu item action hover color */ - --pf-c-select__menu-item--m-action--hover--Color: var(--pf-global--Color--100, #151515); - /** Select menu item action focus color */ - --pf-c-select__menu-item--m-action--focus--Color: var(--pf-global--Color--100, #151515); - /** Select menu item action disabled color */ - --pf-c-select__menu-item--m-action--disabled--Color: var(--pf-global--disabled-color--200, #d2d2d2); - /** Select menu item action width */ - --pf-c-select__menu-item--m-action--Width: auto; - /** Select menu item action font size */ - --pf-c-select__menu-item--m-action--FontSize: var(--pf-global--icon--FontSize--sm, 0.625rem); - /** Select menu item action hover background color */ - --pf-c-select__menu-item--m-action--hover--BackgroundColor: transparent; - /** Select menu item action focus background color */ - --pf-c-select__menu-item--m-action--focus--BackgroundColor: transparent; - /** Select menu item hover action color */ - --pf-c-select__menu-item--hover__menu-item--m-action--Color: var(--pf-global--Color--200, #6a6e73); - /** Select menu item favorite action color */ - --pf-c-select__menu-item--m-favorite-action--Color: var(--pf-global--Color--200, #6a6e73); - /** Select menu item favorite action hover color */ - --pf-c-select__menu-item--m-favorite-action--hover--Color: var(--pf-global--Color--100, #151515); - /** Select menu item favorite action focus color */ - --pf-c-select__menu-item--m-favorite-action--focus--Color: var(--pf-global--Color--100, #151515); - /** Select menu wrapper favorite action color */ - --pf-c-select__menu-wrapper--m-favorite__menu-item--m-favorite-action--Color: var(--pf-global--palette--gold-400, #f0ab00); - /** Select menu wrapper favorite action hover color */ - --pf-c-select__menu-wrapper--m-favorite__menu-item--m-favorite-action--hover--Color: var(--pf-global--palette--gold-500, #c58c00); - /** Select menu wrapper favorite action focus color */ - --pf-c-select__menu-wrapper--m-favorite__menu-item--m-favorite-action--focus--Color: var(--pf-global--palette--gold-500, #c58c00); - /** Select menu item load color */ - --pf-c-select__menu-item--m-load--Color: var(--pf-global--link--Color, #06c); - /** Select menu item icon color */ - --pf-c-select__menu-item-icon--Color: var(--pf-global--active-color--100, #06c); - /** Select menu item icon font size */ - --pf-c-select__menu-item-icon--FontSize: var(--pf-global--icon--FontSize--sm, 0.625rem); - /** Select menu item icon right position */ - --pf-c-select__menu-item-icon--Right: var(--pf-global--spacer--md, 1rem); - /** Select menu item icon top position */ - --pf-c-select__menu-item-icon--Top: 50%; - /** Select menu item icon transform Y */ - --pf-c-select__menu-item-icon--TranslateY: -50%; - /** Select menu item action icon minimum height */ - --pf-c-select__menu-item-action-icon--MinHeight: calc(var(--pf-c-select__menu-item--FontSize) * var(--pf-c-select__menu-item--LineHeight)); - /** Select menu item match font weight */ - --pf-c-select__menu-item--match--FontWeight: var(--pf-global--FontWeight--bold, 700); - /** Select menu search top padding */ - --pf-c-select__menu-search--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu search right padding */ - --pf-c-select__menu-search--PaddingRight: var(--pf-c-select__menu-item--PaddingRight); - /** Select menu search bottom padding */ - --pf-c-select__menu-search--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** Select menu search left padding */ - --pf-c-select__menu-search--PaddingLeft: var(--pf-c-select__menu-item--PaddingLeft); - /** Select menu group top padding */ - --pf-c-select__menu-group--menu-group--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu group title top padding */ - --pf-c-select__menu-group-title--PaddingTop: var(--pf-c-select__menu-item--PaddingTop); - /** Select menu group title right padding */ - --pf-c-select__menu-group-title--PaddingRight: var(--pf-c-select__menu-item--PaddingRight); - /** Select menu group title bottom padding */ - --pf-c-select__menu-group-title--PaddingBottom: var(--pf-c-select__menu-item--PaddingBottom); - /** Select menu group title left padding */ - --pf-c-select__menu-group-title--PaddingLeft: var(--pf-c-select__menu-item--PaddingLeft); - /** Select menu group title font size */ - --pf-c-select__menu-group-title--FontSize: var(--pf-global--FontSize--xs, 0.75rem); - /** Select menu group title font weight */ - --pf-c-select__menu-group-title--FontWeight: var(--pf-global--FontWeight--normal, 400); - /** Select menu group title color */ - --pf-c-select__menu-group-title--Color: var(--pf-global--Color--dark-200, #6a6e73); - /** Select menu item count left margin */ - --pf-c-select__menu-item-count--MarginLeft: var(--pf-global--spacer--md, 1rem); - /** Select menu item count font size */ - --pf-c-select__menu-item-count--FontSize: var(--pf-global--FontSize--sm, 0.875rem); - /** Select menu item count color */ - --pf-c-select__menu-item-count--Color: var(--pf-global--Color--200, #6a6e73); - /** Select menu item disabled count color */ - --pf-c-select__menu-item--disabled__menu-item-count--Color: var(--pf-global--Color--dark-200, #6a6e73); - /** Select menu item description font size */ - --pf-c-select__menu-item-description--FontSize: var(--pf-global--FontSize--xs, 0.75rem); - /** Select menu item description color */ - --pf-c-select__menu-item-description--Color: var(--pf-global--Color--200, #6a6e73); - /** Select menu item description right padding */ - --pf-c-select__menu-item-description--PaddingRight: var(--pf-c-select__menu-item--PaddingRight); - /** Select menu item main right padding */ - --pf-c-select__menu-item-main--PaddingRight: var(--pf-c-select__menu-item--PaddingRight); - /** Select menu item selected main right padding */ - --pf-c-select__menu-item--m-selected__menu-item-main--PaddingRight: var(--pf-c-select__menu-item--m-selected--PaddingRight); - /** Select menu footer box shadow */ - --pf-c-select__menu-footer--BoxShadow: var(--pf-global--BoxShadow--sm-top, 0 -0.125rem 0.25rem -0.0625rem rgba(3, 3, 3, 0.16)); - /** Select menu footer top padding */ - --pf-c-select__menu-footer--PaddingTop: var(--pf-global--spacer--md, 1rem); - /** Select menu footer right padding */ - --pf-c-select__menu-footer--PaddingRight: var(--pf-global--spacer--md, 1rem); - /** Select menu footer bottom padding */ - --pf-c-select__menu-footer--PaddingBottom: var(--pf-global--spacer--md, 1rem); - /** Select menu footer left padding */ - --pf-c-select__menu-footer--PaddingLeft: var(--pf-global--spacer--md, 1rem); - /** Select menu footer top margin */ - --pf-c-select__menu-footer--MarginTop: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu footer bottom margin */ - --pf-c-select__menu-footer--MarginBottom: calc(var(--pf-global--spacer--sm, 0.5rem) * -1); - /** Select menu divider top margin */ - --pf-c-select-menu--c-divider--MarginTop: var(--pf-global--spacer--sm, 0.5rem); - /** Select menu divider bottom margin */ - --pf-c-select-menu--c-divider--MarginBottom: var(--pf-global--spacer--sm, 0.5rem); -} - -:host, #outer { - display: flex; - flex-direction: column; - align-items: stretch; -} - -:host([hidden]), -*[hidden] { - display: none !important; -} - -:host([disabled]) { - pointer-events: none !important; -} - -#outer.disabled { - color: var(--pf-global--Color--dark-200, #6a6e73) !important; -} - -#outer { - position: relative; -} - -/* TODO(bennyp): see if we can get rid of this wrapping node, for perf reasons */ -#listbox-container { - display: inline-flex; - border: 1px solid var(--pf-global--BorderColor--100, #d2d2d2); - position: absolute; - background-color: var(--pf-theme--color--surface--lightest, #fff) !important; - opacity: 0; - --_active-descendant-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important -} - -#outer.expanded #listbox-container { - opacity: 1; - z-index: 9999 !important; -} - -#listbox { - display: flex; - flex-direction: column; - position: relative; - width: 100%; -} - -#listbox slot.disabled { - color: var(--pf-c-list__item-icon--Color, #6a6e73) !important; - background-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - border-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - pointer-events: none; - cursor: not-allowed;; - --_active-descendant-color: transparent; - --_svg-color: var(--pf-c-list__item-icon--Color, #6a6e73) !important; -} - - -#toggle { - background-color: var(--pf-theme--color--surface--lightest, #fff) !important; -} - -#toggle, -#toggle-button, -#toggle-input { - display: flex; - align-items: center; - font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); - font-size: var(--pf-global--FontSize--md, 16px); - font-weight: var(--pf-global--FontWeight--normal, 400); - line-height: 1.6; -} - -#toggle { - border: 1px solid var(--pf-global--BorderColor--100, #d2d2d2); - border-bottom-color: var(--pf-theme--color--text, #151515); - justify-content: space-between; -} - -.expanded #toggle { - border-bottom-width: 2px; - border-bottom-color: var(--pf-theme--color--accent, #0066cc); -} - -.disabled #toggle { - color: var(--pf-global--Color--dark-200, #6a6e73) !important; - background-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; - border-color: var(--pf-theme--color--surface--lighter, #f0f0f0) !important; -} - -#toggle-input, -#toggle-button { - background: transparent; - border: none; - text-align: left; - border-radius: 0; - flex: 1 0 auto; - min-height: 44px; - min-width: 44px; -} - -#toggle-input { - justify-content: space-between; - padding: var(--pf-global--spacer--xs, 0.25rem) var(--pf-global--spacer--sm, 0.5rem); -} - -.disabled #toggle-input { - pointer-events: none; -} - -#toggle-button { - color: currentColor; - background-color: transparent; - justify-content: flex-end; - padding: var(--pf-global--spacer--sm, 0.5rem); -} - -#toggle-button:focus:before { - border-bottom-color: var(--pf-c-select__toggle--focus--before--BorderBottomColor); - border-bottom-width: var(--pf-c-select__toggle--focus--before--BorderBottomWidth); -} - -#outer.typeahead #toggle-button { - flex: 0 0 auto; -} - -#toggle-badge { - flex: 1 0 auto; - margin-inline-start: 0.25em; -} - -#toggle-text { - flex: 1 1 auto; -} - -#toggle-text.badge { - flex: 0 1 auto; -} - -pf-badge { - padding: 0; -} - -#toggle svg { - width: 1em; - height: 1em; - flex: 0 0 auto; - margin-inline-start: 1em; -} - -#description { - display: block; -} - -#listbox.checkboxes { - --_pf-option-checkboxes-display: inline; - --_pf-option-svg-display: none; -} - -::slotted(pf-option-group + hr) { - display: none !important; -} - -::slotted(hr:has(+ pf-option-group)) { - display: none !important; -} - -.visually-hidden { - border: 0; - clip: rect(0, 0, 0, 0); - block-size: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - white-space: nowrap; - inline-size: 1px; -} - -::slotted(hr) { - --pf-c-divider--BorderWidth--base: var(--pf-global--BorderWidth--sm, 1px); - --pf-c-divider--BorderColor--base: var(--pf-c-divider--BackgroundColor); - --pf-c-divider--Height: var(--pf-c-divider--BorderWidth--base); - --pf-c-divider--BackgroundColor: var(--pf-global--BorderColor--100, #d2d2d2); - --pf-c-divider--after--BackgroundColor: var(--pf-c-divider--BorderColor--base); - --pf-c-divider--after--FlexBasis: 100%; - --pf-c-divider--after--Inset: 0%; - --pf-c-divider--m-vertical--after--FlexBasis: 100%; - --pf-c-divider--m-horizontal--Display: flex; - --pf-c-divider--m-horizontal--FlexDirection: row; - --pf-c-divider--m-horizontal--after--Height: var(--pf-c-divider--Height); - --pf-c-divider--m-horizontal--after--Width: auto; - --pf-c-divider--m-vertical--Display: inline-flex; - --pf-c-divider--m-vertical--FlexDirection: column; - --pf-c-divider--m-vertical--after--Height: auto; - --pf-c-divider--m-vertical--after--Width: var(--pf-c-divider--BorderWidth--base); - --pf-hidden-visible--visible--Display: var(--pf-c-divider--Display); - --pf-c-divider--Display: var(--pf-c-divider--m-horizontal--Display); - --pf-c-divider--FlexDirection: var(--pf-c-divider--m-horizontal--FlexDirection); - --pf-c-divider--after--Width: var(--pf-c-divider--m-horizontal--after--Width); - --pf-c-divider--after--Height: var(--pf-c-divider--m-horizontal--after--Height); - display: var(--pf-c-divider--Display, flex); - flex-direction: var(--pf-c-divider--FlexDirection); - border: 0; - width: 100%; - margin-top: var(--pf-c-select-menu--c-divider--MarginTop); - margin-bottom: var(--pf-c-select-menu--c-divider--MarginBottom); -} - -::slotted(hr)::after { - content: ''; - width: var(--pf-c-divider--after--Width, 100%) !important; - height: var(--pf-c-divider--after--Height, 1px); - background-color: var(--pf-c-divider--after--BackgroundColor); - flex: 1 0 100%; -} diff --git a/elements/pf-select/pf-select.ts b/elements/pf-select/pf-select.ts deleted file mode 100644 index 0bcf091e47..0000000000 --- a/elements/pf-select/pf-select.ts +++ /dev/null @@ -1,395 +0,0 @@ -import type { PfChipGroup } from '../pf-chip/pf-chip-group.js'; -import type { Placement } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; -import type { TemplateResult } from 'lit'; - -import { LitElement, html, isServer, nothing } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; -import { query } from 'lit/decorators/query.js'; -import { repeat } from 'lit/directives/repeat.js'; -import { styleMap } from 'lit/directives/style-map.js'; -import { classMap } from 'lit/directives/class-map.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; - -import { ComboboxController } from '@patternfly/pfe-core/controllers/combobox-controller.js'; -import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; -import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; - -import { arraysAreEquivalent } from '@patternfly/pfe-core/functions/arraysAreEquivalent.js'; -import { observes } from '@patternfly/pfe-core/decorators/observes.js'; - -import { PfOption } from './pf-option.js'; -import { type PfChip, PfChipRemoveEvent } from '../pf-chip/pf-chip.js'; - -import styles from './pf-select.css'; - -export class PfSelectChangeEvent extends Event { - constructor() { - super('change', { bubbles: true }); - } -} - -/** - * A select list enables users to select one or more items from a list. - * - * A select component consists of a toggle control to open and close a menu of actions or links. - * Selects differ from dropdowns in that they persist selection, - * whereas dropdowns are typically used to present a list of actions or links. - * @alias Select - * @fires open - when the menu toggles open - * @fires close - when the menu toggles closed - */ -@customElement('pf-select') -export class PfSelect extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static readonly formAssociated = true; - - static override readonly shadowRootOptions: ShadowRootInit = { - ...LitElement.shadowRootOptions, - delegatesFocus: true, - }; - - /** Variant of rendered Select */ - @property() variant: 'single' | 'checkbox' | 'typeahead' | 'typeaheadmulti' = 'single'; - - /** Accessible label for the select */ - @property({ attribute: 'accessible-label' }) accessibleLabel?: string; - - /** Accessible label for chip group used to describe chips */ - @property({ - attribute: 'accessible-current-selections-label', - }) accessibleCurrentSelectionsLabel = 'Current selections'; - - /** Multi listbox button text */ - @property({ attribute: 'items-selected-text' }) itemsSelectedText = 'items selected'; - - /** Whether the select is disabled */ - @property({ type: Boolean, reflect: true }) disabled = false; - - /** Whether the select listbox is expanded */ - @property({ type: Boolean, reflect: true }) expanded = false; - - /** - * Enable to flip listbox when it reaches boundary - */ - @property({ attribute: 'enable-flip', type: Boolean }) enableFlip = false; - - /** Current form value */ - @property() value?: string; - - /** Placeholder entry. Overridden by the `placeholder` slot */ - @property() placeholder?: string; - - /** - * Indicates initial popover position. - * There are 6 options: `bottom`, `top`, `top-start`, `top-end`, `bottom-start`, `bottom-end`. - * Default is `bottom`. - */ - @property({ reflect: true }) position: Placement = 'bottom'; - - /** Flag indicating if selection badge should be hidden for checkbox variant,default false */ - @property({ - attribute: 'checkbox-selection-badge-hidden', - type: Boolean, - }) checkboxSelectionBadgeHidden = false; - - @query('pf-chip-group') private _chipGroup?: PfChipGroup; - - @query('#toggle-input') private _toggleInput?: HTMLInputElement; - - @query('#toggle-button') private _toggleButton?: HTMLButtonElement; - - @query('#listbox') private _listbox?: HTMLElement; - - @query('#listbox-container') private _listboxContainer?: HTMLElement; - - @query('#placeholder') private _placeholder?: PfOption; - - #isNotPlaceholderOption = (option: PfOption) => option !== this._placeholder; - - #internals = InternalsController.of(this); - - #float = new FloatingDOMController(this, { content: () => this._listboxContainer }); - - #slots = new SlotController(this, null, 'placeholder'); - - #combobox = ComboboxController.of(this, { - multi: this.variant === 'typeaheadmulti' || this.variant === 'checkbox', - getItems: () => this.options, - getFallbackLabel: () => this.accessibleLabel - || this.#internals.computedLabelText - || this.placeholder - || this.#slots.getSlotted('placeholder').map(x => x.textContent).join(''), - getListboxElement: () => this._listbox ?? null, - getToggleButton: () => this._toggleButton ?? null, - getComboboxInput: () => this._toggleInput ?? null, - isExpanded: () => this.expanded, - requestShowListbox: () => void (this.expanded ||= true), - requestHideListbox: () => void (this.expanded &&= false), - setItemHidden: (item, hidden) => (item.id !== 'placeholder') && void (item.hidden = hidden), - isItem: item => item instanceof PfOption, - setItemActive: (item, active) => item.active = active, - setItemSelected: (item, selected) => item.selected = selected, - }); - - /** - * Single select option value for single select menus, - * or array of select option values for multi select. - */ - @property({ hasChanged: (a, b) => !arraysAreEquivalent(a, b) }) - set selected(selected: PfOption | PfOption[]) { - const list = Array.isArray(selected) ? selected : [selected]; - this.#combobox.selected = list; - } - - get selected(): PfOption[] { - return this.#combobox.selected; - } - - /** List of options */ - get options(): PfOption[] { - if (isServer) { - return []; // TODO: expose a DOM property to allow setting options in SSR scenarios - } else { - return [ - this._placeholder, - ...Array.from(this.querySelectorAll('pf-option')), - ].filter((x): x is PfOption => !!x && !x.hidden); - } - } - - /** Whether select has badge for number of selected items */ - get #hasBadge() { - // NOTE: revisit this in v5 - return this.variant === 'checkbox' && !this.checkboxSelectionBadgeHidden; - } - - get #buttonLabel(): string { - const { selected } = this.#combobox; - switch (this.variant) { - case 'typeaheadmulti': - return `${selected?.length ?? 0} ${this.itemsSelectedText}`; - case 'checkbox': - return this.#computePlaceholderText() - || 'Options'; - default: - return (selected ? this.value : '') - || this.#computePlaceholderText() - || 'Select a value'; - } - } - - override render(): TemplateResult<1> { - const { disabled, expanded, variant, placeholder } = this; - const { anchor = 'bottom', alignment = 'start', styles = {} } = this.#float; - const { height, width } = this.getBoundingClientRect?.() || {}; - const hasBadge = this.#hasBadge; - const selectedOptions = this.#combobox.selected ?? []; - const typeahead = variant.startsWith('typeahead'); - const checkboxes = variant === 'checkbox'; - const badge = hasBadge && 'badge'; - const hasSelection = !!(Array.isArray(this.selected) ? this.selected.length : this.selected); - const hideLightDomItems = typeahead && !ComboboxController.supportsCrossRootActiveDescendant; - const placeholderIsInert = !placeholder && this.#slots.isEmpty('placeholder'); - - return html` -
-
- ${!(typeahead && selectedOptions.length < 1) ? '' : html` - - ${repeat(selectedOptions, opt => opt.id, opt => html` - ${opt.textContent}`)} - `} - ${!typeahead ? '' : html` - `} - -
-
-
- ${placeholder ?? ''} - ${this.#combobox.renderItemsToShadowRoot()} - - -
-
-
- `; - } - - @observes('disabled') - private disabledChanged() { - this.#combobox.disabled = this.disabled; - } - - @observes('expanded') - private async expandedChanged(old: boolean, expanded: boolean) { - if (this.dispatchEvent(new Event(this.expanded ? 'close' : 'open'))) { - if (expanded) { - this.#doExpand(); - } else { - this.#doCollapse(); - } - } - } - - @observes('selected') - private async selectedChanged(_: PfOption[], selected: PfOption[]) { - this.value = selected.map(x => x.value).join(); - await this.updateComplete; - switch (this.variant) { - case 'single': { - // Only focus toggle when closing after user selection; avoid stealing focus on init. - const wasExpanded = this.expanded; - this.hide(); - if (wasExpanded) { - this._toggleButton?.focus(); - } - break; - } - case 'typeahead': - this._toggleInput!.value = this.value; - } - } - - @observes('variant') - private async variantChanged() { - this.#combobox.hostDisconnected(); - this.#combobox.multi = this.variant === 'typeaheadmulti' || this.variant === 'checkbox'; - this.#combobox.hostConnected(); - if (this.variant === 'checkbox') { - import('@patternfly/elements/pf-badge/pf-badge.js'); - } - } - - @observes('value') - private valueChanged() { - this.#internals.setFormValue(this.value ?? ''); - this.dispatchEvent(new PfSelectChangeEvent()); - } - - @observes('variant') - @observes('value') - private focusChips(): void { - // whether select has removable chips for selected items - // NOTE: revisit this in v5 - // reset input if chip has been added - const hasChips = this.variant === 'typeaheadmulti'; - if (hasChips && this._toggleInput?.value) { - const chip = - this.shadowRoot?.querySelector(`pf-chip#chip-${this._toggleInput?.value}`) as PfChip; - if (chip && this._chipGroup) { - this._chipGroup.focusOnChip(chip); - this._toggleInput.value = ''; - } - } - } - - async #doExpand() { - try { - await this.#float.show({ placement: this.position || 'bottom', flip: !!this.enableFlip }); - return true; - } catch { - return false; - } - } - - async #doCollapse() { - try { - await this.#float.hide(); - return true; - } catch { - return false; - } - } - - /** - * handles chip's remove button clicking - * @param event remove event - * @param opt pf-option - */ - #onChipRemove(opt: PfOption, event: Event) { - if (event instanceof PfChipRemoveEvent) { - opt.selected = false; - this._toggleInput?.focus(); - } - } - - #computePlaceholderText() { - return this.placeholder - || this.querySelector?.('[slot=placeholder]') - ?.assignedNodes() - ?.reduce((acc, node) => `${acc}${node.textContent}`, '') - ?.trim() - || this.#combobox.items - .filter(this.#isNotPlaceholderOption) - .at(0) - ?.value - || ''; - } - - /** - * Opens the dropdown - */ - async show(): Promise { - this.expanded = true; - await this.updateComplete; - } - - /** - * Closes listbox - */ - async hide(): Promise { - this.expanded = false; - await this.updateComplete; - } - - /** - * toggles popup based on current state - */ - async toggle(): Promise { - if (this.expanded) { - await this.hide(); - } else { - await this.show(); - } - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-select': PfSelect; - } -} - diff --git a/elements/pf-spinner/README.md b/elements/pf-spinner/README.md deleted file mode 100644 index d6a4a9cd01..0000000000 --- a/elements/pf-spinner/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# PatternFly Elements Spinner - -`` is used to indicate to users that an action is in progress. - -Read more about Spinner in the [PatternFly Elements Spinner documentation](https://patternflyelements.org/components/spinner) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-spinner/pf-spinner.js'; -``` - -## Usage -```html -Loading... -``` - -### Size variations - -```html -Loading... -Loading... -Loading... -Loading... -``` - -### Custom size - -```html -Loading... -``` - diff --git a/elements/pf-spinner/demo/diameter.html b/elements/pf-spinner/demo/diameter.html deleted file mode 100644 index 8b2a6cc957..0000000000 --- a/elements/pf-spinner/demo/diameter.html +++ /dev/null @@ -1,15 +0,0 @@ -
-

Custom size

- Loading... -
- - - - diff --git a/elements/pf-spinner/demo/index.html b/elements/pf-spinner/demo/index.html deleted file mode 100644 index 836cd467aa..0000000000 --- a/elements/pf-spinner/demo/index.html +++ /dev/null @@ -1,14 +0,0 @@ -
- Loading... -
- - - - diff --git a/elements/pf-spinner/demo/size.html b/elements/pf-spinner/demo/size.html deleted file mode 100644 index 6b63677aaf..0000000000 --- a/elements/pf-spinner/demo/size.html +++ /dev/null @@ -1,18 +0,0 @@ -
-

Size variations

- Loading... - Loading... - Loading... - Loading... -
- - - - diff --git a/elements/pf-spinner/docs/pf-spinner.md b/elements/pf-spinner/docs/pf-spinner.md deleted file mode 100644 index 0aa5d3241f..0000000000 --- a/elements/pf-spinner/docs/pf-spinner.md +++ /dev/null @@ -1,40 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - A spinner is used to indicate to users that an action is in progress. - - Loading... -{% endrenderOverview %} - -{% band header="Usage" %} - ### Basic - {% htmlexample %} - Loading... - {% endhtmlexample %} - ### Size variations - {% htmlexample %} - Loading... - Loading... - Loading... - Loading... - {% endhtmlexample %} - - ### Custom size - {% htmlexample %} - Loading... - {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderProperties %}{% endrenderProperties %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-spinner/pf-spinner.css b/elements/pf-spinner/pf-spinner.css deleted file mode 100644 index c9d3990ef0..0000000000 --- a/elements/pf-spinner/pf-spinner.css +++ /dev/null @@ -1,99 +0,0 @@ -:host { - display: inline-block; - width: min-content; - min-height: 0; - aspect-ratio: 1 / 1; -} - -[hidden] { - display: none !important; -} - -svg { - overflow: hidden; - /** Width for spinner */ - width: var(--pf-c-spinner--Width, - /** Diameter for spinner */ - var(--pf-c-spinner--diameter, - var(--pf-global--icon--FontSize--xl, 3.375rem))); - /** Height for spinner */ - height: var(--pf-c-spinner--Height, - var(--pf-c-spinner--diameter, - var(--pf-global--icon--FontSize--xl, 3.375rem))); - animation: - pf-c-spinner-animation-rotate - /** Animation duration for spinner */ - calc(var(--pf-c-spinner--AnimationDuration, 1.4s) * 2) - /** Animation timing function for spinner */ - var(--pf-c-spinner--AnimationTimingFunction, linear) infinite; -} - -circle { - width: 100%; - height: 100%; - transform-origin: 50% 50%; - stroke-linecap: round; - stroke-dasharray: 283; - stroke-dashoffset: 280; - /** Color for spinner */ - stroke: var(--pf-c-spinner--Color, var(--pf-global--primary-color--100, #06c)); - /** Stroke width for spinner */ - stroke-width: var(--pf-c-spinner--stroke-width, 10); - animation: - pf-c-spinner-animation-dash - var(--pf-c-spinner--AnimationDuration, 1.4s) - /** Path animation timing function for spinner */ - var(--pf-c-spinner__path--AnimationTimingFunction, ease-in-out) infinite; -} - -:host([size="sm"]) svg { - /** Diameter for small spinner */ - --pf-c-spinner--diameter: var(--pf-c-spinner--m-sm--diameter, - var(--pf-global--icon--FontSize--sm, 0.625rem)); -} - -:host([size="md"]) svg { - /** Diameter for medium spinner */ - --pf-c-spinner--diameter: var(--pf-c-spinner--m-md--diameter, - var(--pf-global--icon--FontSize--md, 1.125rem)); -} - -:host([size="lg"]) svg { - /** Diameter for large spinner */ - --pf-c-spinner--diameter: var(--pf-c-spinner--m-lg--diameter, - var(--pf-global--icon--FontSize--lg, 1.5rem)); -} - -:host([size="xl"]) svg { - /** Diameter for extra large spinner */ - --pf-c-spinner--diameter: var(--pf-c-spinner--m-xl--diameter, - var(--pf-global--icon--FontSize--xl, 3.375rem)); -} - -@keyframes pf-c-spinner-animation-rotate { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -@keyframes pf-c-spinner-animation-dash { - 0% { - stroke-dashoffset: 280; - transform: rotate(0); - } - 15% { - /** Path stroke width for spinner */ - stroke-width: calc(var(--pf-c-spinner__path--StrokeWidth, 10) - 4); - } - 40% { - stroke-dashoffset: 150; - stroke-dasharray: 220; - } - 100% { - stroke-dashoffset: 280; - transform: rotate(720deg); - } -} diff --git a/elements/pf-spinner/pf-spinner.ts b/elements/pf-spinner/pf-spinner.ts deleted file mode 100644 index 62f906825a..0000000000 --- a/elements/pf-spinner/pf-spinner.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { styleMap } from 'lit/directives/style-map.js'; -import { property } from 'lit/decorators/property.js'; - -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import styles from './pf-spinner.css'; - -/** - * A **spinner** is used to indicate to users that an action is in progress. For actions - * that may take a long time, use a progress bar instead. - * @alias Spinner - */ - -@customElement('pf-spinner') -export class PfSpinner extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - // eslint-disable-next-line no-unused-private-class-members - #internals = InternalsController.of(this, { role: 'progressbar' }); - - /** Preset sizes for the spinner */ - @property({ reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'xl'; - - /** Custom diameter of spinner set as CSS variable */ - @property({ reflect: true }) diameter?: `${string}${'px' | '%' | 'rem' | 'em' | 'fr' | 'pt'}`; - - override render(): TemplateResult<1> { - return html` - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pf-spinner': PfSpinner; - } -} diff --git a/elements/pf-spinner/test/pf-spinner.spec.ts b/elements/pf-spinner/test/pf-spinner.spec.ts deleted file mode 100644 index 12b2621499..0000000000 --- a/elements/pf-spinner/test/pf-spinner.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { expect, html } from '@open-wc/testing'; -import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfSpinner } from '@patternfly/elements/pf-spinner/pf-spinner.js'; - -describe('', function() { - it('imperatively instantiates', function() { - expect(document.createElement('pf-spinner')).to.be.an.instanceof(PfSpinner); - }); - - it('should upgrade', async function() { - const element = await createFixture(html`Loading...`); - expect(element, 'pf-spinner should be an instance of PfeSpinner') - .to.be.an.instanceOf(customElements.get('pf-spinner')) - .and - .to.be.an.instanceOf(PfSpinner); - }); - - it('should properly initialize the component', async function() { - const element = await createFixture(html` - Loading... - `); - expect(element.getAttribute('size')).to.equal('xl'); - }); - - describe('basic usage', function() { - let element: PfSpinner; - beforeEach(async function() { - element = await createFixture(html`Loading...`); - }); - it('is accessible', async function() { - await expect(element).to.be.accessible(); - }); - }); - - describe('size attribute', function() { - let element: PfSpinner; - - function convertRemToPixels(rem: `${number}rem`) { - const { fontSize } = getComputedStyle(document.documentElement); - return parseFloat(rem) * parseFloat(fontSize); - } - - beforeEach(async function() { - element = await createFixture(html` - Loading... - `); - }); - - for (const [size, expected] of [ - ['sm', '0.625rem'], - ['md', '1.125rem'], - ['lg', '1.5rem'], - ['xl', '3.375rem'], - ] as const) { - it(size, async function() { - element.size = size; - await element.updateComplete; - expect(element.offsetWidth).to.equal(convertRemToPixels(expected)); - }); - } - }); - - describe('diameter attribute', function() { - it('sets the element diameter', async function() { - const customDiameterValue = 80; - const element = await createFixture(html` - Loading... - `); - - expect(element.offsetWidth).to.equal(customDiameterValue); - }); - }); -}); diff --git a/elements/pf-switch/README.md b/elements/pf-switch/README.md deleted file mode 100644 index caabdba9cd..0000000000 --- a/elements/pf-switch/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# PatternFly Elements Switch - -`` is a switch that toggles the state of a setting (between on and -off). Switches provide a more explicit, visible representation on a setting. - -Read more about Switch in the [PatternFly Elements Switch documentation](https://patternflyelements.org/components/switch) - -## Installation - -Load `` via CDN: - -```html - -``` - -Or, if you are using [NPM](https://npm.im), install it - -```bash -npm install @patternfly/elements -``` - -Then once installed, import it to your application: - -```js -import '@patternfly/elements/pf-switch/pf-switch.js'; -``` - -### Note -For `` to work in Safari, you'll need to load the [element-internals-polyfill](https://www.npmjs.com/package/element-internals-polyfill). Safari is in the process of [adding element internals to WebKit](https://bugs.webkit.org/show_bug.cgi?id=197960) so this polyfill should be temporary. - -## Usage -```html - - - -``` - -### Form Associated - -`` is a form-associated custom element. That means that it can -participate in HTML forms just like a native ``. For example, if you add -the `name` attribute, or the `id` attribute, the element will appear in the -FormData object. For example, if you add the `name` attribute, or the `id` -attribute, the element will appear in the `FormData` object. - -```html -
- - -
-``` - - -### Without label - -```html - -``` - -### Checked with label - -```html - - - -``` - -### Disabled Switches - -```html -
-
- Checked and Disabled - - - -
- -
- - - -
-
-``` diff --git a/elements/pf-switch/demo/checked.html b/elements/pf-switch/demo/checked.html deleted file mode 100644 index c4e718db5f..0000000000 --- a/elements/pf-switch/demo/checked.html +++ /dev/null @@ -1,42 +0,0 @@ -
-
-
- Checked with label - - -
-
-
- - - - diff --git a/elements/pf-switch/demo/disabled.html b/elements/pf-switch/demo/disabled.html deleted file mode 100644 index 28f3862fb0..0000000000 --- a/elements/pf-switch/demo/disabled.html +++ /dev/null @@ -1,48 +0,0 @@ -
-
-
- Checked and Disabled - - -
-
- - -
-
-
- - - - diff --git a/elements/pf-switch/demo/index.html b/elements/pf-switch/demo/index.html deleted file mode 100644 index 5407c73a3a..0000000000 --- a/elements/pf-switch/demo/index.html +++ /dev/null @@ -1,81 +0,0 @@ -
-

A switch toggles the state of a setting (between on and off). Switches provide a more explicit, visible representation on a setting.

-
-
- Option A - - -
- -
- Option B (no explicit label) - -
- -
- Form Disabled State - - - - - - - - - - - -
-
-
- - - - diff --git a/elements/pf-switch/demo/nested-in-label.html b/elements/pf-switch/demo/nested-in-label.html deleted file mode 100644 index 5cfa58d910..0000000000 --- a/elements/pf-switch/demo/nested-in-label.html +++ /dev/null @@ -1,57 +0,0 @@ -
-
-
- Nested in a label - - Submit - Submit to read status -
-
-
- - - - diff --git a/elements/pf-switch/demo/reversed.html b/elements/pf-switch/demo/reversed.html deleted file mode 100644 index d98da7665e..0000000000 --- a/elements/pf-switch/demo/reversed.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
-
- Reversed - - -
-
-
- - - - diff --git a/elements/pf-switch/demo/without-label.html b/elements/pf-switch/demo/without-label.html deleted file mode 100644 index 6662e471e2..0000000000 --- a/elements/pf-switch/demo/without-label.html +++ /dev/null @@ -1,39 +0,0 @@ -
-
-
- Without label - -
-
-
- - - - diff --git a/elements/pf-switch/docs/pf-switch.md b/elements/pf-switch/docs/pf-switch.md deleted file mode 100644 index 236833b15d..0000000000 --- a/elements/pf-switch/docs/pf-switch.md +++ /dev/null @@ -1,70 +0,0 @@ -{% renderInstallation %} {% endrenderInstallation %} - -{% renderOverview %} - A switch toggles the state of a setting (between on and off). Switches - provide a more explicit, visible representation on a setting. - - - -{% endrenderOverview %} - -{% band header="Usage" %} - ### Basic - {% htmlexample %} - - - {% endhtmlexample %} - - ### Without label - {% htmlexample %} - - {% endhtmlexample %} - - ### Checked with label - {% htmlexample %} - - - {% endhtmlexample %} - - ### Disabled - {% htmlexample %} -
-
- Checked and Disabled - - -
-
- - -
-
- {% endhtmlexample %} -{% endband %} - -{% renderSlots %}{% endrenderSlots %} - -{% renderAttributes %}{% endrenderAttributes %} - -{% renderMethods %}{% endrenderMethods %} - -{% renderEvents %}{% endrenderEvents %} - -{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} - -{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-switch/pf-switch.css b/elements/pf-switch/pf-switch.css deleted file mode 100644 index de39871647..0000000000 --- a/elements/pf-switch/pf-switch.css +++ /dev/null @@ -1,154 +0,0 @@ -:host { - display: inline-block; - outline: none; -} - -svg { - fill: currentcolor; -} - -[hidden] { - display: none !important; -} - -:host([checked]) #container { - /** Color of the label when switch is checked */ - color: var(--pf-c-switch__input--checked__label--Color, - var(--pf-global--Color--dark-100, #151515)); - /** Background color of the toggle when switch is checked */ - background-color: var(--pf-c-switch__input--checked__toggle--BackgroundColor, - var(--pf-global--primary-color--100, #06c)); -} - -:host([checked]) #container::before { - /** Translation of the toggle knob when checked */ - translate: var(--pf-c-switch__input--checked__toggle--before--TranslateX, - calc(100% + var(--pf-c-switch__toggle-icon--Offset, 0.125rem))); -} - -:host(:is(:focus,:focus-within)) #container { - /** Focus outline width */ - outline: var(--pf-c-switch__input--focus__toggle--OutlineWidth, - var(--pf-global--BorderWidth--md, 2px)) solid var(--pf-c-switch__input--focus__toggle--OutlineColor, - var(--pf-global--primary-color--100, #06c)); - /** Focus outline offset */ - outline-offset: var(--pf-c-switch__input--focus__toggle--OutlineOffset, - var(--pf-global--spacer--sm, 0.5rem)); -} - -:host(:disabled) { - pointer-events: none; - cursor: not-allowed; -} - -:host(:disabled) #container { - cursor: not-allowed; - - /** Color of the label when switch is disabled */ - color: var(--pf-c-switch__input--disabled__label--Color, - var(--pf-global--disabled-color--100, #6a6e73)); - /** Background color when switch is disabled */ - background-color: var(--pf-c-switch__input--disabled__toggle--BackgroundColor, - var(--pf-global--disabled-color--200, #d2d2d2)); -} - -:host(:disabled) #container::before { - /** Background color of the toggle knob when disabled */ - background-color: var(--pf-c-switch__input--disabled__toggle--before--BackgroundColor, - var(--pf-global--palette--black-150, #f5f5f5)); -} - -:host([checked]:disabled) #container::before { - translate: var(--pf-c-switch__input--checked__toggle--before--TranslateX, - calc(100% + var(--pf-c-switch__toggle-icon--Offset, 0.125rem))); -} - -:host(:disabled:focus-within) #container { - outline: none; -} - -#container { - position: relative; - display: inline-flex; - align-items: center; - /** Width of the toggle */ - width: var(--pf-c-switch__toggle--Width, - calc(var(--pf-c-switch__toggle--Height, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) * var(--pf-c-switch--LineHeight, - var(--pf-global--LineHeight--md, 1.5)))) + var(--pf-c-switch__toggle-icon--Offset, 0.125rem) + var(--pf-c-switch__toggle--before--Width, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) - var(--pf-c-switch__toggle-icon--Offset, 0.125rem))))); - height: var(--pf-c-switch__toggle--Height, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) * var(--pf-c-switch--LineHeight, - var(--pf-global--LineHeight--md, 1.5)))); - /** Background color of the toggle */ - background-color: var(--pf-c-switch__toggle--BackgroundColor, - var(--pf-global--palette--black-500, #8a8d90)); - /** Border radius of the toggle */ - border-radius: var(--pf-c-switch__toggle--BorderRadius, - var(--pf-c-switch__toggle--Height, calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) * var(--pf-c-switch--LineHeight, - var(--pf-global--LineHeight--md, 1.5))))); -} - -#container::before { - position: absolute; - display: block; - content: ""; - /** Top position of the toggle knob */ - top: var(--pf-c-switch__toggle--before--Top, - calc((var(--pf-c-switch__toggle--Height, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) * var(--pf-c-switch--LineHeight, - var(--pf-global--LineHeight--md, 1.5)))) - var(--pf-c-switch__toggle--before--Height, - var(--pf-c-switch__toggle--before--Width, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) - var(--pf-c-switch__toggle-icon--Offset, 0.125rem))))) / 2)); - /** Left position of the toggle knob */ - left: var(--pf-c-switch__toggle--before--Left, - var(--pf-c-switch__toggle--before--Top, - calc((var(--pf-c-switch__toggle--Height, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) * var(--pf-c-switch--LineHeight, - var(--pf-global--LineHeight--md, 1.5)))) - var(--pf-c-switch__toggle--before--Height, - var(--pf-c-switch__toggle--before--Width, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) - var(--pf-c-switch__toggle-icon--Offset, 0.125rem))))) / 2))); - /** Width of the toggle knob */ - width: var(--pf-c-switch__toggle--before--Width, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) - var(--pf-c-switch__toggle-icon--Offset, 0.125rem))); - /** Height of the toggle knob */ - height: var(--pf-c-switch__toggle--before--Height, - var(--pf-c-switch__toggle--before--Width, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) - var(--pf-c-switch__toggle-icon--Offset, 0.125rem)))); - /** Background color of the toggle knob */ - background-color: var(--pf-c-switch__toggle--before--backgroundcolor, - var(--pf-global--backgroundcolor--100, #fff)); - /** Border radius of the toggle knob */ - border-radius: var(--pf-c-switch__toggle--before--BorderRadius, - var(--pf-global--BorderRadius--lg, 30em)); - /** Box shadow of the toggle knob */ - box-shadow: var(--pf-c-switch__toggle--before--BoxShadow, - var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06))); - /** Transition of the toggle knob */ - transition: var(--pf-c-switch__toggle--before--Transition, - var(--pf-c-switch__toggle--before--Transition, translate .25s ease 0s)); -} - -svg { - /** Horizontal margin of the toggle icon */ - margin-inline: var(--pf-c-switch__toggle-icon--Left, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) * .4)); - /** Font size of the toggle icon */ - font-size: var(--pf-c-switch__toggle-icon--FontSize, - calc(var(--pf-c-switch--FontSize, - var(--pf-global--FontSize--md, 1rem)) * .625)); - /** Color of the toggle icon */ - color: var(--pf-c-switch__toggle-icon--Color, - var(--pf-global--Color--light-100, #fff)); -} diff --git a/elements/pf-switch/pf-switch.ts b/elements/pf-switch/pf-switch.ts deleted file mode 100644 index aace554008..0000000000 --- a/elements/pf-switch/pf-switch.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { LitElement, html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators/custom-element.js'; -import { property } from 'lit/decorators/property.js'; - -import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; - -import styles from './pf-switch.css'; - -/** - * A **switch** toggles the state of a setting (between on and off). Switches and - * checkboxes can often be used interchangeably, but the switch provides a more - * explicit, visible representation on a setting. - * @alias Switch - * @fires {Event} change - Fires when the switch selection changes. - */ - -@customElement('pf-switch') -export class PfSwitch extends LitElement { - static readonly styles: CSSStyleSheet[] = [styles]; - - static readonly formAssociated = true; - - declare shadowRoot: ShadowRoot; - - #internals = InternalsController.of(this, { role: 'switch' }); - - /** @deprecated use `accessible-label` instead */ - @property({ reflect: true }) label?: string; - - /** - * Accessible label for the switch when there is no associated `
-
+ diff --git a/elements/pf-background-image/demo/pfbg.jpg b/elements/pf-v5-background-image/demo/pfbg.jpg similarity index 100% rename from elements/pf-background-image/demo/pfbg.jpg rename to elements/pf-v5-background-image/demo/pfbg.jpg diff --git a/elements/pf-background-image/demo/pfbg_1200.jpg b/elements/pf-v5-background-image/demo/pfbg_1200.jpg similarity index 100% rename from elements/pf-background-image/demo/pfbg_1200.jpg rename to elements/pf-v5-background-image/demo/pfbg_1200.jpg diff --git a/elements/pf-background-image/demo/pfbg_576.jpg b/elements/pf-v5-background-image/demo/pfbg_576.jpg similarity index 100% rename from elements/pf-background-image/demo/pfbg_576.jpg rename to elements/pf-v5-background-image/demo/pfbg_576.jpg diff --git a/elements/pf-background-image/demo/pfbg_768.jpg b/elements/pf-v5-background-image/demo/pfbg_768.jpg similarity index 100% rename from elements/pf-background-image/demo/pfbg_768.jpg rename to elements/pf-v5-background-image/demo/pfbg_768.jpg diff --git a/elements/pf-background-image/demo/pfbg_768@2x.jpg b/elements/pf-v5-background-image/demo/pfbg_768@2x.jpg similarity index 100% rename from elements/pf-background-image/demo/pfbg_768@2x.jpg rename to elements/pf-v5-background-image/demo/pfbg_768@2x.jpg diff --git a/elements/pf-v5-background-image/demo/sibling-content.html b/elements/pf-v5-background-image/demo/sibling-content.html new file mode 100644 index 0000000000..5a5a2221ae --- /dev/null +++ b/elements/pf-v5-background-image/demo/sibling-content.html @@ -0,0 +1,30 @@ + + + +
+

Sibling Content

+ Button +
+ + + + + + diff --git a/elements/pf-v5-background-image/docs/pf-v5-background-image.md b/elements/pf-v5-background-image/docs/pf-v5-background-image.md new file mode 100644 index 0000000000..0e45547415 --- /dev/null +++ b/elements/pf-v5-background-image/docs/pf-v5-background-image.md @@ -0,0 +1,65 @@ +{% renderOverview %} + {% htmlexample class="pf-v5-background-image" %} + + {% endhtmlexample %} + + View the [full screen demo](/components/background-image/demo/). + +{% endrenderOverview %} + +{% band header="Usage" %} + ### Sibling content w/ no filter + {% htmlexample class="pf-v5-background-image" %} + +

Sibling Content

+ Button + {% endhtmlexample %} + + View the [full screen demo](/components/background-image/demo/sibling-content/). + + ### Override SVG Filter + + [MDN documentation for ``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter) + + {% htmlexample class="pf-v5-background-image" %} + + + + + + + + {% endhtmlexample %} + + View the [full screen demo](/components/background-image/demo/filter-override/). + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-background-image/docs/screenshot.png b/elements/pf-v5-background-image/docs/screenshot.png similarity index 100% rename from elements/pf-background-image/docs/screenshot.png rename to elements/pf-v5-background-image/docs/screenshot.png diff --git a/elements/pf-v5-background-image/pf-v5-background-image.css b/elements/pf-v5-background-image/pf-v5-background-image.css new file mode 100644 index 0000000000..c787efe3cf --- /dev/null +++ b/elements/pf-v5-background-image/pf-v5-background-image.css @@ -0,0 +1,73 @@ +:host { + display: flex; + + /** Background image for the element */ + --_background-image: var(--pf-v5-c-background-image--BackgroundImage); +} + +#outer-container { + display: contents; +} + +#container { + padding: 0; + margin: 0; + background-color: transparent; +} + +#container::after { + display: block; + position: fixed; + top: 0; + left: 0; + z-index: -1; + width: 100%; + height: 100%; + content: ""; + /** Background color for the background image */ + background-color: var(--pf-v5-c-background-image--BackgroundColor, var(--pf-global--BackgroundColor--dark-100, #151515)); + background-image: var(--_background-image); + /** SVG filter applied to the background image */ + filter: var(--pf-v5-c-background-image--Filter, url("#image_overlay")); + background-repeat: no-repeat; + background-size: cover; +} + +slot[name="filter"] { + display: none; +} + +slot[part="content"] { + display: block; + position: relative; + z-index: 1; + color: white; +} + +@media screen and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + #container::after { + /** Background image for 2x DPI screens */ + background-image: var(--pf-v5-c-background-image--BackgroundImage-2x, var(--_background-image-2x, var(--_background-image))); + } +} + +@media screen and (min-width: 576px) { + #container::after { + /** Background image for small screens */ + background-image: var(--pf-v5-c-background-image--BackgroundImage--sm, var(--_background-image-sm, var(--_background-image))); + } +} + +@media screen and (min-width: 576px) and (-webkit-min-device-pixel-ratio: 2), (min-width: 576px) and (min-resolution: 192dpi) { + #container::after { + /** Background image for small screens with 2x DPI */ + background-image: var(--pf-v5-c-background-image--BackgroundImage--sm-2x, var(--_background-image-sm-2x, var(--_background-image))); + } +} + +@media screen and (min-width: 992px) { + #container::after { + /** Background image for large screens */ + background-image: var(--pf-v5-c-background-image--BackgroundImage--lg, var(--_background-image-lg, var(--_background-image))); + } +} diff --git a/elements/pf-v5-background-image/pf-v5-background-image.ts b/elements/pf-v5-background-image/pf-v5-background-image.ts new file mode 100644 index 0000000000..f961c31998 --- /dev/null +++ b/elements/pf-v5-background-image/pf-v5-background-image.ts @@ -0,0 +1,115 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { queryAssignedElements } from 'lit/decorators/query-assigned-elements.js'; +import { property } from 'lit/decorators/property.js'; + + +import { styleMap, type StyleInfo } from 'lit/directives/style-map.js'; + +import styles from './pf-v5-background-image.css'; + +/** + * A **background image** allows you to place an image in the background of your page or area of a page. + * @summary Allows users to place an image in the background of your page or area of a page. + * @alias Background Image + */ +@customElement('pf-v5-background-image') +export class PfV5BackgroundImage extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + /** + * The URL for the image shown on mobile + */ + @property({ reflect: true }) src?: string; + + /** + * The image shown on mobile with 2x DPI + */ + @property({ reflect: true, attribute: 'src-2x' }) src2x?: string; + + /** + * The URL for the image shown on small screens (min-width: 576px) + */ + @property({ reflect: true, attribute: 'src-sm' }) srcSm?: string; + + /** + * The URL for the image shown on small screens (min-width: 576px) with 2x DPI + */ + @property({ reflect: true, attribute: 'src-sm-2x' }) srcSm2x?: string; + + /** + * The URL for the image shown on large screens (min-width: 992px) + */ + @property({ reflect: true, attribute: 'src-lg' }) srcLg?: string; + + /** + * Apply SVG Filter to the image + */ + @property({ type: Boolean, reflect: true }) filter = false; + + @queryAssignedElements({ slot: 'filter', selector: 'svg' }) private _svg?: SVGElement[]; + + #svg?: SVGElement; + + #updated = false; + + render(): TemplateResult<1> { + const cssProps = { + '--_background-image': this.src, + '--_background-image-2x': this.src2x, + '--_background-image-sm': this.srcSm, + '--_background-image-sm-2x': this.srcSm2x, + '--_background-image-lg': this.srcLg, + } as StyleInfo; + + Object.entries(cssProps).forEach(([key, value]) => { + // if the value is undefined, remove the css property + if (!value) { + delete cssProps[key]; + } else { + // otherwise, add the value with the url() css function + cssProps[key] = `url(${value})`; + } + }); + + return html` +
+ ${!this.filter ? html`` : html` + + + ${(this.#svg && this.#updated) ? this.#svg : html` + + + + + + + + + + + + `} + + `} +
+ `; + } + + #onSlotChange() { + const [svg] = this._svg as SVGElement[]; + if (svg) { + this.#svg = svg; + this.#updated = true; + this.requestUpdate(); + } else { + this.#updated = false; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-background-image': PfV5BackgroundImage; + } +} diff --git a/elements/pf-background-image/test/pf-background-image.e2e.ts b/elements/pf-v5-background-image/test/pf-background-image.e2e.ts similarity index 94% rename from elements/pf-background-image/test/pf-background-image.e2e.ts rename to elements/pf-v5-background-image/test/pf-background-image.e2e.ts index c6b83ffb54..9f7bc5a7a0 100644 --- a/elements/pf-background-image/test/pf-background-image.e2e.ts +++ b/elements/pf-v5-background-image/test/pf-background-image.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-background-image'; +const tagName = 'pf-v5-background-image'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-background-image/test/pf-background-image.spec.ts b/elements/pf-v5-background-image/test/pf-background-image.spec.ts similarity index 80% rename from elements/pf-background-image/test/pf-background-image.spec.ts rename to elements/pf-v5-background-image/test/pf-background-image.spec.ts index 8d2f6e2b24..23acf3b66c 100644 --- a/elements/pf-background-image/test/pf-background-image.spec.ts +++ b/elements/pf-v5-background-image/test/pf-background-image.spec.ts @@ -1,17 +1,17 @@ import { expect, html } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfBackgroundImage } from '@patternfly/elements/pf-background-image/pf-background-image.js'; +import { PfV5BackgroundImage } from '@patternfly/elements/pf-v5-background-image/pf-v5-background-image.js'; import { setViewport } from '@web/test-runner-commands'; const example = html` - - + `; function isMobile() { @@ -29,21 +29,21 @@ function isDesktop() { return matches; } -describe('', function() { +describe('', function() { describe('simply instantiating', function() { - let element: PfBackgroundImage; + let element: PfV5BackgroundImage; it('imperatively instantiates', function() { - expect(document.createElement('pf-background-image')).to.be.an.instanceof(PfBackgroundImage); + expect(document.createElement('pf-v5-background-image')).to.be.an.instanceof(PfV5BackgroundImage); }); it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-background-image'); + element = await createFixture(html``); + const klass = customElements.get('pf-v5-background-image'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfBackgroundImage); + .to.be.an.instanceOf(PfV5BackgroundImage); }); describe('adjusting window size', function() { @@ -55,7 +55,7 @@ describe('', function() { }); it('should background image', async function() { - const element = await createFixture(example); + const element = await createFixture(example); await element.updateComplete; const container = element.shadowRoot!.querySelector('#container')!; const afterStyles = getComputedStyle(container, '::after'); @@ -72,7 +72,7 @@ describe('', function() { }); it('should background image', async function() { - const element = await createFixture(example); + const element = await createFixture(example); await element.updateComplete; const container = element.shadowRoot!.querySelector('#container')!; const afterStyles = getComputedStyle(container, '::after'); @@ -89,7 +89,7 @@ describe('', function() { }); it('should background image', async function() { - const element = await createFixture(example); + const element = await createFixture(example); await element.updateComplete; const container = element.shadowRoot!.querySelector('#container')!; const afterStyles = getComputedStyle(container, '::after'); diff --git a/elements/pf-v5-badge/README.md b/elements/pf-v5-badge/README.md new file mode 100644 index 0000000000..41650198ac --- /dev/null +++ b/elements/pf-v5-badge/README.md @@ -0,0 +1,57 @@ +# PatternFly Elements Badge + +The `` component provides a way to have small numerical descriptors for +for for for UI elements. To provide context to your badge, it is highly +encouraged that you also include an `aria-label` attribute in your markup. + +Read more about Badge in the [PatternFly Elements Badge documentation](https://patternflyelements.org/components/badge) + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-badge/pf-v5-badge.js'; +``` + +## Usage + +```html +
+ 2 +
+``` + +Please refer to the [specification](https://www.w3.org/TR/wai-aria/#aria-label) for additional details. + +With the `threshold` attribute: + +```html +
+ 1 + 17 + 900 +
+``` + +With two state options for the `state` attribute: + +```html +
+ 10 + 20 +
+``` + diff --git a/elements/pf-v5-badge/demo/index.html b/elements/pf-v5-badge/demo/index.html new file mode 100644 index 0000000000..0976ef0f30 --- /dev/null +++ b/elements/pf-v5-badge/demo/index.html @@ -0,0 +1,12 @@ +
+ 613 +
+ + + diff --git a/elements/pf-v5-badge/demo/read.html b/elements/pf-v5-badge/demo/read.html new file mode 100644 index 0000000000..d8f5029bcc --- /dev/null +++ b/elements/pf-v5-badge/demo/read.html @@ -0,0 +1,15 @@ +
+ 7 + 24 + 240 + 999 +
+ + + diff --git a/elements/pf-v5-badge/demo/threshold.html b/elements/pf-v5-badge/demo/threshold.html new file mode 100644 index 0000000000..2132e3679a --- /dev/null +++ b/elements/pf-v5-badge/demo/threshold.html @@ -0,0 +1,16 @@ +
+ 400 + 900 +

In this demo, the threshold is set to 500. + It should add '+' sign if the value exceeds the threshold. + It shouldn't add a '+' sign if the value doesn't exceed the threshold

+
+ + + diff --git a/elements/pf-v5-badge/demo/unread.html b/elements/pf-v5-badge/demo/unread.html new file mode 100644 index 0000000000..826b244593 --- /dev/null +++ b/elements/pf-v5-badge/demo/unread.html @@ -0,0 +1,15 @@ +
+ 7 + 24 + 240 + 999 +
+ + + diff --git a/elements/pf-badge/docs/CHANGELOG.old.md b/elements/pf-v5-badge/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-badge/docs/CHANGELOG.old.md rename to elements/pf-v5-badge/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-badge/docs/pf-v5-badge.md b/elements/pf-v5-badge/docs/pf-v5-badge.md new file mode 100644 index 0000000000..02d5080fdc --- /dev/null +++ b/elements/pf-v5-badge/docs/pf-v5-badge.md @@ -0,0 +1,43 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + 17 + 900 + 10 +{% endrenderOverview %} + +{% band header="Usage" %} + To provide context to your badge, it is highly encouraged that you also include an `aria-label` attribute in your markup. + + ### Default + {% htmlexample %} + 2 + {% endhtmlexample %} + + ### With a threshold + This adds a "+" next to the number once the threshold value has been passed. + + {% htmlexample %} + 20 + {% endhtmlexample %} + + ### With a state + This adds a background color to the badge based on the state. + + {% htmlexample %} + 10 + 20 + {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-badge/docs/screenshot.png b/elements/pf-v5-badge/docs/screenshot.png similarity index 100% rename from elements/pf-badge/docs/screenshot.png rename to elements/pf-v5-badge/docs/screenshot.png diff --git a/elements/pf-v5-badge/pf-v5-badge.css b/elements/pf-v5-badge/pf-v5-badge.css new file mode 100644 index 0000000000..732ed346ca --- /dev/null +++ b/elements/pf-v5-badge/pf-v5-badge.css @@ -0,0 +1,51 @@ +:host { + position: relative; + white-space: nowrap; + text-align: center; + display: inline-block; + /** Border radius for badge */ + border-radius: var(--pf-v5-c-badge--BorderRadius, + var(--pf-global--BorderRadius--lg, 180em)); + /** Minimum width for badge */ + min-width: var(--pf-v5-c-badge--MinWidth, + var(--pf-global--spacer--xl, 2rem)); + /** Left padding for badge */ + padding-left: var(--pf-v5-c-badge--PaddingLeft, + var(--pf-global--spacer--sm, 0.5rem)); + /** Right padding for badge */ + padding-right: var(--pf-v5-c-badge--PaddingRight, + var(--pf-global--spacer--sm, 0.5rem)); + /** Font size for badge */ + font-size: var(--pf-v5-c-badge--FontSize, + var(--pf-v5-theme--font-size, 0.75em)); + /** Font weight for badge */ + font-weight: var(--pf-v5-c-badge--FontWeight, + var(--pf-v5-theme--font-weight--bold, 700)); + /** Line height for badge */ + line-height: var(--pf-v5-c-badge--LineHeight, + var(--pf-global--LineHeight--md, 1.5)); + /** Color for badge */ + color: var(--pf-v5-c-badge--Color, + var(--pf-global--palette--black-900, #151515)); + /** Background color for badge */ + background-color: var(--pf-v5-c-badge--BackgroundColor, + var(--pf-global--palette--black-200, #f0f0f0)); +} + +:host([state="read"]) { + /** Color for read badge */ + --pf-v5-c-badge--Color: var(--pf-v5-c-badge--m-read--Color, + var(--pf-global--palette--black-900, #151515)); + /** Background color for read badge */ + --pf-v5-c-badge--BackgroundColor: var(--pf-v5-c-badge--m-read--BackgroundColor, + var(--pf-global--palette--black-200, #f0f0f0)); +} + +:host([state="unread"]) { + /** Color for unread badge */ + --pf-v5-c-badge--Color: var(--pf-v5-c-badge--m-unread--Color, + var(--pf-global--palette--white, #fff)); + /** Background color for unread badge */ + --pf-v5-c-badge--BackgroundColor: var(--pf-v5-c-badge--m-unread--BackgroundColor, + var(--pf-global--palette--blue-400, #06c)); +} diff --git a/elements/pf-v5-badge/pf-v5-badge.ts b/elements/pf-v5-badge/pf-v5-badge.ts new file mode 100644 index 0000000000..b2fd42a183 --- /dev/null +++ b/elements/pf-v5-badge/pf-v5-badge.ts @@ -0,0 +1,53 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import styles from './pf-v5-badge.css'; + +/** + * A **badge** is used to annotate other information like a label or an object name. + * @alias Badge + */ + + +@customElement('pf-v5-badge') +export class PfV5Badge extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + /** + * Denotes the state-of-affairs this badge represents + * Options include read and unread + */ + @property({ reflect: true }) state?: 'unread' | 'read'; + + /** + * Sets a numeric value for a badge. + * + * You can pair it with `threshold` attribute to add a `+` sign + * if the number exceeds the threshold value. + */ + @property({ reflect: true, type: Number }) number?: number; + + /** + * Sets a threshold for the numeric value and adds `+` sign if + * the numeric value exceeds the threshold value. + */ + @property({ reflect: true, type: Number }) threshold?: number; + + override render(): TemplateResult<1> { + const { threshold, number, textContent } = this; + const displayText = + (threshold && number && (threshold < number)) ? `${threshold.toString()}+` + : (number != null) ? number.toString() + : textContent ?? ''; + return html` + ${displayText} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-badge': PfV5Badge; + } +} diff --git a/elements/pf-badge/test/pf-badge.e2e.ts b/elements/pf-v5-badge/test/pf-badge.e2e.ts similarity index 95% rename from elements/pf-badge/test/pf-badge.e2e.ts rename to elements/pf-v5-badge/test/pf-badge.e2e.ts index d2723a0326..cbf4e4d311 100644 --- a/elements/pf-badge/test/pf-badge.e2e.ts +++ b/elements/pf-v5-badge/test/pf-badge.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-badge'; +const tagName = 'pf-v5-badge'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-badge/test/pf-badge.spec.ts b/elements/pf-v5-badge/test/pf-badge.spec.ts new file mode 100644 index 0000000000..5eb0726cb2 --- /dev/null +++ b/elements/pf-v5-badge/test/pf-badge.spec.ts @@ -0,0 +1,61 @@ +import { expect, html, nextFrame } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { hexToRgb, getColor } from '@patternfly/pfe-tools/test/hex-to-rgb.js'; +import { PfV5Badge } from '@patternfly/elements/pf-v5-badge/pf-v5-badge.js'; + +// Background colors for the various states +const states = { + default: '#f0f0f0', + read: '#f0f0f0', + unread: '#0066cc', +}; + +const element = html`10`; + +describe('', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-badge')).to.be.an.instanceof(PfV5Badge); + }); + + it('should upgrade', async function() { + const el = await createFixture(element); + expect(el, 'pf-v5-badge should be an instance of PfV5Badge') + .to.be.an + .instanceOf(customElements.get('pf-v5-badge')) + .and + .to.be.an.instanceof(PfV5Badge); + }); + + it('should display text equivalent to the number attribute', async function() { + const el = await createFixture(html`10`); + await nextFrame(); + expect(el.shadowRoot!.querySelector('span')!.textContent).to.equal('100'); + }); + + it('should add \'+\' sign if the value exceeds the threshold', async function() { + const el = await createFixture(html`900`); + await nextFrame(); + expect(el.shadowRoot!.querySelector('span')!.textContent).to.equal('100+'); + }); + + it('shouldn\'t add a \'+\' sign if the value doesn\'t exceed the threshold', async function() { + const el = await createFixture(html`900`); + await el.updateComplete; + expect(el.textContent).to.equal('900'); + expect(el.shadowRoot!.querySelector('span')!.textContent).to.equal('900'); + }); + + // This is the one that created an error: + Object.entries(states).forEach(([state, color]) => { + it(`should have a background color of '${color}' when state is ${state}`, async function() { + const el = await createFixture(element); + + if (state !== 'default') { + el.setAttribute('state', state); + } + + const [r, g, b] = getColor(el, 'background-color'); + expect([r, g, b]).to.deep.equal(hexToRgb(color)); + }); + }); +}); diff --git a/elements/pf-v5-banner/README.md b/elements/pf-v5-banner/README.md new file mode 100644 index 0000000000..75d8362cd5 --- /dev/null +++ b/elements/pf-v5-banner/README.md @@ -0,0 +1,60 @@ +# Patternfly Elements Banner +A banner is a 1-line, full color, full width container that can be used to communicate short snippets of information to users. Banners are un-intrusive and non-dismissible. + +Read more about Button in the [PatternFly Elements Banner documentation](https://patternflyelements.org/components/banner) + +## Installation +Load `` via CDN: + +```html + +``` +Or, if you are using [NPM](https://npm.im/), install it + +```bash +npm install @patternfly/elements +``` +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-banner/pf-v5-banner.js'; +``` + + +## Usage + +```html + + Default banner + +``` + + +## Attributes + +### Variant +Banners have five different variants. The available variants are `default`, `info`, `success`, `danger`, and `warning`. + +```html + + Info banner + +``` + +### Icon +Banners can have an icon attribute for a shorthand to Patternfly Icons. To see available icons, visit the [Patternfly Elements Icons documentation](https://patternflyelements.com/components/icons/). + +```html + + Info banner + +``` + +### Sticky +Banners can be sticky, so they stick to the top of the parent. + +```html + + Sticky banner + +``` diff --git a/elements/pf-v5-banner/demo/index.html b/elements/pf-v5-banner/demo/index.html new file mode 100644 index 0000000000..0cbdbf711e --- /dev/null +++ b/elements/pf-v5-banner/demo/index.html @@ -0,0 +1,15 @@ +Default Banner +Blue Banner +Red Banner +Green Banner +Gold Banner + + + + diff --git a/elements/pf-v5-banner/demo/status-banners.html b/elements/pf-v5-banner/demo/status-banners.html new file mode 100644 index 0000000000..1c2c3d68e4 --- /dev/null +++ b/elements/pf-v5-banner/demo/status-banners.html @@ -0,0 +1,40 @@ + + + Default Banner + + + + + Info Banner + + + + + Danger Banner + + + + + Success Banner + + + + + Warning Banner + + + + + diff --git a/elements/pf-banner/demo/sticky.html b/elements/pf-v5-banner/demo/sticky.html similarity index 97% rename from elements/pf-banner/demo/sticky.html rename to elements/pf-v5-banner/demo/sticky.html index 30494e7e6c..3f00ceaf4c 100644 --- a/elements/pf-banner/demo/sticky.html +++ b/elements/pf-v5-banner/demo/sticky.html @@ -1,4 +1,4 @@ -I stick to the top +I stick to the top

The Open Source Definition

https://opensource.org/osd/ @@ -51,5 +51,5 @@

10. License Must Be Technology-Neutral

Version 1.9, last modified, 2007-03-22

Here’s the historical “Annotated OSD” from the early 2000’s. diff --git a/elements/pf-v5-banner/docs/pf-v5-banner.md b/elements/pf-v5-banner/docs/pf-v5-banner.md new file mode 100644 index 0000000000..c2c4924ee3 --- /dev/null +++ b/elements/pf-v5-banner/docs/pf-v5-banner.md @@ -0,0 +1,59 @@ +{% renderOverview %} + Default +{% endrenderOverview %} + +{% band header="Usage" %} + {% htmlexample %} + Default + {% endhtmlexample %} + + + ### With a variant + Available variants are: default, info, danger, success, warning + + {% htmlexample %} + Default + Info + Danger + Success + Warning + {% endhtmlexample %} + + + ### Sticky + Banners can be set to stick to the top of their container by adding the `sticky` attribute. + + {% htmlexample %} + Sticky + {% endhtmlexample %} + + + ### With an icon + You can add a icon by using the shorthand icon attribute + + {% htmlexample %} + Info + {% endhtmlexample %} + + Or you can use the icon slot and slot in a `svg` or `pf-v5-icon` element + + {% htmlexample %} + + + Info + + {% endhtmlexample %} + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-banner/docs/screenshot.png b/elements/pf-v5-banner/docs/screenshot.png similarity index 100% rename from elements/pf-banner/docs/screenshot.png rename to elements/pf-v5-banner/docs/screenshot.png diff --git a/elements/pf-v5-banner/pf-v5-banner.css b/elements/pf-v5-banner/pf-v5-banner.css new file mode 100644 index 0000000000..6b67a56f9a --- /dev/null +++ b/elements/pf-v5-banner/pf-v5-banner.css @@ -0,0 +1,117 @@ +:host { + display: block; + /** Top padding for banner */ + --pf-v5-c-banner--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); + /** Right padding for banner */ + --pf-v5-c-banner--PaddingRight: var(--pf-global--spacer--md, 1rem); + /** Right padding for banner on medium screens */ + --pf-v5-c-banner--md--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); + /** Bottom padding for banner */ + --pf-v5-c-banner--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + /** Left padding for banner */ + --pf-v5-c-banner--PaddingLeft: var(--pf-global--spacer--md, 1rem); + /** Left padding for banner on medium screens */ + --pf-v5-c-banner--md--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); + /** Font size for banner */ + --pf-v5-c-banner--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** Text color for banner */ + --pf-v5-c-banner--Color: var(--pf-global--Color--100, #151515); + /** Background color for banner */ + --pf-v5-c-banner--BackgroundColor: var(--pf-global--BackgroundColor--dark-400, #4f5255); + /** Link color for banner (references banner color) */ + --pf-v5-c-banner--link--Color: var(--pf-v5-c-banner--Color); + /** Link text decoration for banner */ + --pf-v5-c-banner--link--TextDecoration: underline; + /** Link hover color for banner (references banner color) */ + --pf-v5-c-banner--link--hover--Color: var(--pf-v5-c-banner--Color); + /** Link hover font weight for banner */ + --pf-v5-c-banner--link--hover--FontWeight: var(--pf-global--FontWeight--semi-bold, 700); + /** Disabled link color for banner (references banner color) */ + --pf-v5-c-banner--link--disabled--Color: var(--pf-v5-c-banner--Color); + /** Disabled link text decoration for banner */ + --pf-v5-c-banner--link--disabled--TextDecoration: none; + /** Background color for info banner */ + --pf-v5-c-banner--m-info--BackgroundColor: var(--pf-global--palette--blue-200, #73bcf7); + /** Background color for danger banner */ + --pf-v5-c-banner--m-danger--BackgroundColor: var(--pf-global--danger-color--100, #c9190b); + /** Background color for success banner */ + --pf-v5-c-banner--m-success--BackgroundColor: var(--pf-global--success-color--100, #3e8635); + /** Background color for warning banner */ + --pf-v5-c-banner--m-warning--BackgroundColor: var(--pf-global--warning-color--100, #f0ab00); + /** Z-index for sticky banner */ + --pf-v5-c-banner--m-sticky--ZIndex: var(--pf-global--ZIndex--md, 300); + /** Box shadow for sticky banner */ + --pf-v5-c-banner--m-sticky--BoxShadow: var(--pf-global--BoxShadow--md-bottom); +} + +#container, +#container.default { + color: var(--pf-global--Color--100, var(--pf-global--Color--light-100, #ffffff)); + overflow: hidden; + text-overflow: ellipsis; + padding: + var(--pf-v5-c-banner--PaddingTop, var(--pf-global--spacer--xs, 0.25rem)) + var(--pf-v5-c-banner--PaddingRight, var(--pf-global--spacer--md, 1rem)) + var(--pf-v5-c-banner--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem)) + var(--pf-v5-c-banner--PaddingLeft, var(--pf-global--spacer--md, 1rem)); + font-size: var(--pf-v5-c-banner--FontSize, var(--pf-global--FontSize--sm, 0.875rem)); + color: var(--pf-global--Color--100, var(--pf-global--Color--light-100, #ffffff)); + white-space: nowrap; + background-color: var(--pf-v5-c-banner--BackgroundColor, var(--pf-global--BackgroundColor--dark-400, #4f5255)); + + --pf-v5-icon--size: 1em; +} + +#container.info { + color: var(--pf-global--Color--100, var(--pf-global--Color--dark-100, #151515)); + --pf-v5-c-banner--BackgroundColor: var(--pf-v5-c-banner--m-info--BackgroundColor, var(--pf-global--palette--blue-200, #73bcf7)); +} + +#container.danger { + --pf-v5-c-banner--BackgroundColor: var(--pf-v5-c-banner--m-danger--BackgroundColor, var(--pf-global--danger-color--100, #c9190b)); +} + +#container.success { + --pf-v5-c-banner--BackgroundColor: var(--pf-v5-c-banner--m-success--BackgroundColor, var(--pf-global--success-color--100, #3e8635)); +} + +#container.warning { + color: var(--pf-global--Color--100, var(--pf-global--Color--dark-100, #151515)); + --pf-v5-c-banner--BackgroundColor: var(--pf-v5-c-banner--m-warning--BackgroundColor, var(--pf-global--warning-color--100, #f0ab00)); +} + +#container.hasIcon { + display: var(--pf-v5-l-flex--Display, flex); + flex-wrap: var(--pf-v5-l-flex--FlexWrap, wrap); + align-items: var(--pf-v5-l-flex--AlignItems, baseline); + gap: var(--pf-v5-l-flex--spacer, var(--pf-v5-l-flex--spacer--sm, var(--pf-global--spacer--sm, 0.5rem))); +} + +:host([sticky]) { + position: sticky; + top: 0; + z-index: var(--pf-v5-c-banner--m-sticky--ZIndex, var(--pf-global--ZIndex--md, 300)); + box-shadow: + var(--pf-v5-c-banner--m-sticky--BoxShadow, + var(--pf-global--BoxShadow--md-bottom, 0 0.5rem 0.5rem -0.375rem rgba(3, 3, 3, 0.18))); +} + +pf-v5-icon, +::slotted(pf-v5-icon), +::slotted(svg) { + position: relative; + inset-block-start: 0.125em; +} + +::slotted(svg) { + height: 1em; + width: 1em; + fill: currentcolor; +} + +@media (min-width: 768px) { + #container { + --pf-v5-c-banner--PaddingRight: var(--pf-v5-c-banner--md--PaddingRight, var(--pf-global--spacer--lg, 1.5rem)); + --pf-v5-c-banner--PaddingLeft: var(--pf-v5-c-banner--md--PaddingLeft, var(--pf-global--spacer--lg, 1.5rem)); + } +} diff --git a/elements/pf-v5-banner/pf-v5-banner.ts b/elements/pf-v5-banner/pf-v5-banner.ts new file mode 100644 index 0000000000..5f4f7b2d06 --- /dev/null +++ b/elements/pf-v5-banner/pf-v5-banner.ts @@ -0,0 +1,69 @@ +import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; + +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import styles from './pf-v5-banner.css'; + +export type BannerVariant = ( + | 'default' + | 'info' + | 'warning' + | 'danger' + | 'success' +); + +/** + * A banner is a 1-line, full color, full width container that can be used to communicate short snippets of information to users. + * Banners are un-intrusive and non-dismissible. + * @summary Allows users to display a stylized banner. + * @alias Banner + */ +@customElement('pf-v5-banner') +export class PfV5Banner extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + /** Changes the visual appearance of the banner. */ + @property({ reflect: true }) variant?: BannerVariant; + + /** Shorthand for the `icon` slot, the value is icon name */ + @property() icon?: string; + + /** Shorthand for the `icon` slot, the value is icon name */ + @property({ type: Boolean }) sticky = false; + + /** Represents the state of the anonymous and icon slots */ + #slots = new SlotController(this, null, 'icon'); + + override willUpdate(changed: PropertyValues): void { + if (changed.has('icon') && this.icon) { + import('@patternfly/elements/pf-v5-icon/pf-v5-icon.js'); + } + } + + override render(): TemplateResult<1> { + const { variant, icon } = this; + const hasIcon = !!icon || this.#slots.hasSlotted('icon'); + return html` + +

+ + ${!this.icon ? '' : html` + `} + + + +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-banner': PfV5Banner; + } +} diff --git a/elements/pf-banner/test/pf-banner.e2e.ts b/elements/pf-v5-banner/test/pf-banner.e2e.ts similarity index 95% rename from elements/pf-banner/test/pf-banner.e2e.ts rename to elements/pf-v5-banner/test/pf-banner.e2e.ts index e45c6deb82..c833252a2c 100644 --- a/elements/pf-banner/test/pf-banner.e2e.ts +++ b/elements/pf-v5-banner/test/pf-banner.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-banner'; +const tagName = 'pf-v5-banner'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-banner/test/pf-banner.spec.ts b/elements/pf-v5-banner/test/pf-banner.spec.ts new file mode 100644 index 0000000000..5a6bcd4d1e --- /dev/null +++ b/elements/pf-v5-banner/test/pf-banner.spec.ts @@ -0,0 +1,86 @@ +import { expect, html } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { getColor, hexToRgb } from '@patternfly/pfe-tools/test/hex-to-rgb.js'; +import { PfV5Banner } from '@patternfly/elements/pf-v5-banner/pf-v5-banner.js'; + +describe('', function() { + describe('simply instantiating', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-banner')).to.be.an.instanceof(PfV5Banner); + }); + + it('should upgrade', async function() { + const el = await createFixture(html` + Default + `); + const klass = customElements.get('pf-v5-banner'); + expect(el) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV5Banner); + }); + + describe('without variant attribute', function() { + let element: PfV5Banner; + beforeEach(async function() { + element = await createFixture(html` + Default + `); + await element.updateComplete; + }); + // TODO: remove assertions on shadow roots + it('should display default variant', async function() { + const container = element.shadowRoot!.querySelector('#container')!; + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#ffffff')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#4f5255')); + }); + }); + + describe('with variant="info" attribute', function() { + let element: PfV5Banner; + beforeEach(async function() { + element = await createFixture(html` + Info + `); + await element.updateComplete; + }); + // TODO: remove assertions on shadow roots + it('should display info variant', async function() { + const container = element.shadowRoot!.querySelector('#container')!; + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#73bcf7')); + }); + }); + + describe('with variant="danger" attribute', function() { + let element: PfV5Banner; + beforeEach(async function() { + element = await createFixture(html` + Info + `); + await element.updateComplete; + }); + // TODO: remove assertions on shadow roots + it('should display danger variant', async function() { + const container = element.shadowRoot!.querySelector('#container')!; + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#ffffff')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#c9190b')); + }); + }); + + describe('with valid icon attribute', function() { + let element: PfV5Banner; + beforeEach(async function() { + element = await createFixture(html` + Info + `); + await element.updateComplete; + }); + // TODO: remove assertions on shadow roots + it('should render an icon', async function() { + const icon = element.shadowRoot!.querySelector('pf-v5-icon'); + expect(icon).to.exist; + }); + }); + }); +}); diff --git a/elements/pf-v5-button/README.md b/elements/pf-v5-button/README.md new file mode 100644 index 0000000000..7c51266f5b --- /dev/null +++ b/elements/pf-v5-button/README.md @@ -0,0 +1,61 @@ +# PatternFly Elements Button + +`` is a web component wrapper around a standard HTML ` + ) +} +``` + + + + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} diff --git a/elements/pf-button/docs/screenshot.png b/elements/pf-v5-button/docs/screenshot.png similarity index 100% rename from elements/pf-button/docs/screenshot.png rename to elements/pf-v5-button/docs/screenshot.png diff --git a/elements/pf-v5-button/pf-v5-button-icon.css b/elements/pf-v5-button/pf-v5-button-icon.css new file mode 100644 index 0000000000..d6519719d7 --- /dev/null +++ b/elements/pf-v5-button/pf-v5-button-icon.css @@ -0,0 +1,11 @@ +/****************************** + * * + * ICON * + * * + ******************************/ + +.hasIcon { + gap: calc(2 * var(--pf-v5-c-button__icon--m-start--MarginLeft, + var(--pf-global--spacer--xs, 0.25rem))); +} + diff --git a/elements/pf-v5-button/pf-v5-button-tokens.css b/elements/pf-v5-button/pf-v5-button-tokens.css new file mode 100644 index 0000000000..c3332d148a --- /dev/null +++ b/elements/pf-v5-button/pf-v5-button-tokens.css @@ -0,0 +1,211 @@ +:host { + /** PaddingTop for the button */ + --pf-v5-c-button--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); + /** PaddingRight for the button */ + --pf-v5-c-button--PaddingRight: var(--pf-global--spacer--md, 1rem); + /** PaddingBottom for the button */ + --pf-v5-c-button--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); + /** PaddingLeft for the button */ + --pf-v5-c-button--PaddingLeft: var(--pf-global--spacer--md, 1rem); + /** LineHeight for the button */ + --pf-v5-c-button--LineHeight: var(--pf-global--LineHeight--md, 1.5); + /** FontWeight for the button */ + --pf-v5-c-button--FontWeight: var(--pf-global--FontWeight--normal, 400); + /** FontSize for the button */ + --pf-v5-c-button--FontSize: var(--pf-global--FontSize--md, 1rem); + /** BackgroundColor for the button */ + --pf-v5-c-button--BackgroundColor: transparent; + /** BorderRadius for the button */ + --pf-v5-c-button--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + /** BorderRadius for the button after element */ + --pf-v5-c-button--after--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + /** BorderColor for the button after element */ + --pf-v5-c-button--after--BorderColor: transparent; + /** BorderWidth for the button after element */ + --pf-v5-c-button--after--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); + /** BorderWidth for the button after element on hover */ + --pf-v5-c-button--hover--after--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + /** BorderWidth for the button after element on focus */ + --pf-v5-c-button--focus--after--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + /** BorderWidth for the button after element when active */ + --pf-v5-c-button--active--after--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + /** Color for the button when disabled */ + --pf-v5-c-button--disabled--Color: var(--pf-global--disabled-color--100, #6a6e73); + /** BackgroundColor for the button when disabled */ + --pf-v5-c-button--disabled--BackgroundColor: var(--pf-global--disabled-color--200, #d2d2d2); + /** BorderColor for the button after element when disabled */ + --pf-v5-c-button--disabled--after--BorderColor: transparent; + /** BackgroundColor for the primary button variant */ + --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-global--primary-color--100, #06c); + /** Color for the primary button variant */ + --pf-v5-c-button--m-primary--Color: var(--pf-global--Color--light-100, #fff); + /** BackgroundColor for the primary button variant on hover */ + --pf-v5-c-button--m-primary--hover--BackgroundColor: var(--pf-global--primary-color--200, #004080); + /** Color for the primary button variant on hover */ + --pf-v5-c-button--m-primary--hover--Color: var(--pf-global--Color--light-100, #fff); + /** BackgroundColor for the primary button variant on focus */ + --pf-v5-c-button--m-primary--focus--BackgroundColor: var(--pf-global--primary-color--200, #004080); + /** Color for the primary button variant on focus */ + --pf-v5-c-button--m-primary--focus--Color: var(--pf-global--Color--light-100, #fff); + /** BackgroundColor for the primary button variant when active */ + --pf-v5-c-button--m-primary--active--BackgroundColor: var(--pf-global--primary-color--200, #004080); + /** Color for the primary button variant when active */ + --pf-v5-c-button--m-primary--active--Color: var(--pf-global--Color--light-100, #fff); + /** BackgroundColor for the secondary button variant */ + --pf-v5-c-button--m-secondary--BackgroundColor: transparent; + /** BorderColor for the secondary button variant after element */ + --pf-v5-c-button--m-secondary--after--BorderColor: var(--pf-global--primary-color--100, #06c); + /** Color for the secondary button variant */ + --pf-v5-c-button--m-secondary--Color: var(--pf-global--primary-color--100, #06c); + /** BackgroundColor for the secondary button variant on hover */ + --pf-v5-c-button--m-secondary--hover--BackgroundColor: transparent; + /** BorderColor for the secondary button variant after element on hover */ + --pf-v5-c-button--m-secondary--hover--after--BorderColor: var(--pf-global--primary-color--100, #06c); + /** Color for the secondary button variant on hover */ + --pf-v5-c-button--m-secondary--hover--Color: var(--pf-global--primary-color--100, #06c); + /** BackgroundColor for the secondary button variant on focus */ + --pf-v5-c-button--m-secondary--focus--BackgroundColor: transparent; + /** BorderColor for the secondary button variant after element on focus */ + --pf-v5-c-button--m-secondary--focus--after--BorderColor: var(--pf-global--primary-color--100, #06c); + /** Color for the secondary button variant on focus */ + --pf-v5-c-button--m-secondary--focus--Color: var(--pf-global--primary-color--100, #06c); + /** BackgroundColor for the secondary button variant when active */ + --pf-v5-c-button--m-secondary--active--BackgroundColor: transparent; + --pf-v5-c-button--m-secondary--active--after--BorderColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-button--m-secondary--active--Color: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-button--m-secondary--m-danger--BackgroundColor: transparent; + --pf-v5-c-button--m-secondary--m-danger--Color: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-button--m-secondary--m-danger--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-button--m-secondary--m-danger--hover--BackgroundColor: transparent; + --pf-v5-c-button--m-secondary--m-danger--hover--Color: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-secondary--m-danger--hover--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-button--m-secondary--m-danger--focus--BackgroundColor: transparent; + --pf-v5-c-button--m-secondary--m-danger--focus--Color: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-secondary--m-danger--focus--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-button--m-secondary--m-danger--active--BackgroundColor: transparent; + --pf-v5-c-button--m-secondary--m-danger--active--Color: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-secondary--m-danger--active--after--BorderColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-button--m-tertiary--BackgroundColor: transparent; + --pf-v5-c-button--m-tertiary--after--BorderColor: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-tertiary--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-tertiary--hover--BackgroundColor: transparent; + --pf-v5-c-button--m-tertiary--hover--after--BorderColor: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-tertiary--hover--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-tertiary--focus--BackgroundColor: transparent; + --pf-v5-c-button--m-tertiary--focus--after--BorderColor: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-tertiary--focus--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-tertiary--active--BackgroundColor: transparent; + --pf-v5-c-button--m-tertiary--active--after--BorderColor: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-tertiary--active--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-warning--BackgroundColor: var(--pf-global--warning-color--100, #f0ab00); + --pf-v5-c-button--m-warning--Color: var(--pf-global--Color--dark-100, #151515); + --pf-v5-c-button--m-warning--hover--BackgroundColor: var(--pf-global--palette--gold-500, #c58c00); + --pf-v5-c-button--m-warning--hover--Color: var(--pf-global--Color--dark-100, #151515); + --pf-v5-c-button--m-warning--focus--BackgroundColor: var(--pf-global--palette--gold-500, #c58c00); + --pf-v5-c-button--m-warning--focus--Color: var(--pf-global--Color--dark-100, #151515); + --pf-v5-c-button--m-warning--active--BackgroundColor: var(--pf-global--palette--gold-500, #c58c00); + --pf-v5-c-button--m-warning--active--Color: var(--pf-global--Color--dark-100, #151515); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-button--m-danger--Color: var(--pf-global--Color--light-100, #fff); + --pf-v5-c-button--m-danger--hover--BackgroundColor: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-danger--hover--Color: var(--pf-global--Color--light-100, #fff); + --pf-v5-c-button--m-danger--focus--BackgroundColor: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-danger--focus--Color: var(--pf-global--Color--light-100, #fff); + --pf-v5-c-button--m-danger--active--BackgroundColor: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-danger--active--Color: var(--pf-global--Color--light-100, #fff); + --pf-v5-c-button--m-link--BackgroundColor: transparent; + --pf-v5-c-button--m-link--Color: var(--pf-global--link--Color, #06c); + --pf-v5-c-button--m-link--hover--BackgroundColor: transparent; + --pf-v5-c-button--m-link--hover--Color: var(--pf-global--link--Color--hover, #004080); + --pf-v5-c-button--m-link--focus--BackgroundColor: transparent; + --pf-v5-c-button--m-link--focus--Color: var(--pf-global--link--Color--hover, #004080); + --pf-v5-c-button--m-link--active--BackgroundColor: transparent; + --pf-v5-c-button--m-link--active--Color: var(--pf-global--link--Color--hover, #004080); + --pf-v5-c-button--m-link--disabled--BackgroundColor: transparent; + --pf-v5-c-button--m-link--m-inline--FontSize: inherit; + --pf-v5-c-button--m-link--m-inline--hover--TextDecoration: var(--pf-global--link--TextDecoration--hover, underline); + --pf-v5-c-button--m-link--m-inline--hover--Color: var(--pf-global--link--Color--hover, #004080); + --pf-v5-c-button--m-link--m-inline--PaddingTop: 0; + --pf-v5-c-button--m-link--m-inline--PaddingRight: 0; + --pf-v5-c-button--m-link--m-inline--PaddingBottom: 0; + --pf-v5-c-button--m-link--m-inline--PaddingLeft: 0; + --pf-v5-c-button--m-link--m-inline__progress--Left: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-button--m-link--m-inline--m-in-progress--PaddingLeft: calc(var(--pf-v5-c-button--m-link--m-inline__progress--Left) + 1rem + var(--pf-global--spacer--sm, 0.5rem)); + --pf-v5-c-button--m-link--m-danger--BackgroundColor: transparent; + --pf-v5-c-button--m-link--m-danger--Color: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-button--m-link--m-danger--hover--BackgroundColor: transparent; + --pf-v5-c-button--m-link--m-danger--hover--Color: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-link--m-danger--focus--BackgroundColor: transparent; + --pf-v5-c-button--m-link--m-danger--focus--Color: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-link--m-danger--active--BackgroundColor: transparent; + --pf-v5-c-button--m-link--m-danger--active--Color: var(--pf-global--danger-color--200, #a30000); + --pf-v5-c-button--m-plain--BackgroundColor: transparent; + --pf-v5-c-button--m-plain--Color: var(--pf-global--Color--200, #6a6e73); + --pf-v5-c-button--m-plain--hover--BackgroundColor: transparent; + --pf-v5-c-button--m-plain--hover--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-plain--focus--BackgroundColor: transparent; + --pf-v5-c-button--m-plain--focus--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-plain--active--BackgroundColor: transparent; + --pf-v5-c-button--m-plain--active--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-plain--disabled--Color: var(--pf-global--disabled-color--200, #d2d2d2); + --pf-v5-c-button--m-plain--disabled--BackgroundColor: transparent; + --pf-v5-c-button--m-control--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); + --pf-v5-c-button--m-control--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-control--BorderRadius: 0; + --pf-v5-c-button--m-control--after--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-button--m-control--after--BorderTopColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-button--m-control--after--BorderRightColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-button--m-control--after--BorderBottomColor: var(--pf-global--BorderColor--200, #8a8d90); + --pf-v5-c-button--m-control--after--BorderLeftColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-button--m-control--disabled--BackgroundColor: var(--pf-global--disabled-color--300, #f0f0f0); + --pf-v5-c-button--m-control--hover--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); + --pf-v5-c-button--m-control--hover--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-control--hover--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-button--m-control--hover--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-button--m-control--active--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); + --pf-v5-c-button--m-control--active--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-control--active--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-button--m-control--active--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-button--m-control--focus--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); + --pf-v5-c-button--m-control--focus--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-control--focus--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-button--m-control--focus--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-button--m-control--m-expanded--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); + --pf-v5-c-button--m-control--m-expanded--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-button--m-control--m-expanded--after--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-button--m-control--m-expanded--after--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-button--m-small--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + --pf-v5-c-button--m-display-lg--PaddingTop: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-button--m-display-lg--PaddingRight: var(--pf-global--spacer--xl, 2rem); + --pf-v5-c-button--m-display-lg--PaddingBottom: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-button--m-display-lg--PaddingLeft: var(--pf-global--spacer--xl, 2rem); + --pf-v5-c-button--m-display-lg--FontWeight: var(--pf-global--FontWeight--bold, 700); + --pf-v5-c-button--m-link--m-display-lg--FontSize: var(--pf-global--FontSize--lg, 1.125rem); + --pf-v5-c-button__icon--m-start--MarginRight: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-button__icon--m-end--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-button__progress--width: calc(var(--pf-global--icon--FontSize--md, 1.125rem) + var(--pf-global--spacer--sm, 0.5rem)); + --pf-v5-c-button__progress--Opacity: 0; + --pf-v5-c-button__progress--TranslateY: -50%; + --pf-v5-c-button__progress--TranslateX: 0; + --pf-v5-c-button__progress--Top: 50%; + --pf-v5-c-button__progress--Left: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-button--m-progress--TransitionProperty: padding; + --pf-v5-c-button--m-progress--TransitionDuration: var(--pf-global--TransitionDuration, 250ms); + --pf-v5-c-button--m-progress--PaddingRight: calc(var(--pf-global--spacer--md, 1rem) + var(--pf-v5-c-button__progress--width) / 2); + --pf-v5-c-button--m-progress--PaddingLeft: calc(var(--pf-global--spacer--md, 1rem) + var(--pf-v5-c-button__progress--width) / 2); + --pf-v5-c-button--m-in-progress--PaddingRight: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-button--m-in-progress--PaddingLeft: calc(var(--pf-global--spacer--md, 1rem) + var(--pf-v5-c-button__progress--width)); + --pf-v5-c-button--m-in-progress--m-plain--Color: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-button--m-in-progress--m-plain__progress--Left: 50%; + --pf-v5-c-button--m-in-progress--m-plain__progress--TranslateX: -50%; + --pf-v5-c-button__count--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-button--disabled__c-badge--Color: var(--pf-global--Color--dark-100, #151515); + --pf-v5-c-button--disabled__c-badge--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); + --pf-v5-c-button--m-primary__c-badge--BorderColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-button--m-primary__c-badge--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); + + /** Internal button color property */ + --_button-color: var(--pf-v5-c-button--m-primary--Color); + /** Internal button background color property */ + --_button-background-color: var(--pf-v5-c-button--m-primary--BackgroundColor); +} diff --git a/elements/pf-v5-button/pf-v5-button.css b/elements/pf-v5-button/pf-v5-button.css new file mode 100644 index 0000000000..b0cd26d64f --- /dev/null +++ b/elements/pf-v5-button/pf-v5-button.css @@ -0,0 +1,611 @@ +:host { + font-size: var(--pf-v5-c-button--FontSize); + font-weight: var(--pf-v5-c-button--FontWeight); + line-height: var(--pf-v5-c-button--LineHeight); + text-align: center; + white-space: nowrap; + font-size: var(--pf-v5-c-button--FontSize, + var(--pf-global--FontSize--md, 1rem)); + font-weight: var(--pf-v5-c-button--FontWeight, + var(--pf-global--FontWeight--normal, 400)); + line-height: var(--pf-v5-c-button--LineHeight, + var(--pf-global--LineHeight--md, 1.5)); + display: inline-block; + height: max-content; + cursor: pointer; + position: relative; + font-family: inherit; + border-width: 0; + border-style: solid; + border-radius: var(--pf-v5-c-button--BorderRadius, + var(--pf-global--BorderRadius--sm, 3px)); +} + +:host([hidden]), +[hidden] { + display: none !important; +} + +:host([inline]) { + display: inline; + --pf-v5-c-button--PaddingTop: 0; + --pf-v5-c-button--PaddingLeft: 0; + --pf-v5-c-button--PaddingBottom: 0; + --pf-v5-c-button--PaddingRight: 0; +} + +pf-v5-icon, +::slotted(pf-v5-icon) { + color: currentcolor; + padding-inline-start: var(--_button-icon-padding-inline-start); + padding-inline-end: var(--_button-icon-padding-inline-end); + vertical-align: var(--_button-icon-vertical-align); +} + +#icon { + margin-inline-end: var(--pf-v5-c-button__icon--m-start--MarginRight, + var(--pf-global--spacer--xs, 0.25rem)); +} + +#button { + display: inline-block; + color: var(--_button-color); + padding: + var(--pf-v5-c-button--PaddingTop, + var(--pf-global--spacer--form-element, 0.375rem)) + var(--pf-v5-c-button--PaddingRight, + var(--pf-global--spacer--md, 1rem)) + var(--pf-v5-c-button--PaddingBottom, + var(--pf-global--spacer--form-element, 0.375rem)) + var(--pf-v5-c-button--PaddingLeft, + var(--pf-global--spacer--md, 1rem)); + &::before, + &::after { + position: absolute; + inset: 0; + content: ""; + } + &::before { + background-color: var(--_button-background-color); + border-radius: var(--pf-v5-c-button--BorderRadius, + var(--pf-global--BorderRadius--sm, 3px)); + } + &::after { + pointer-events: none; + border-style: solid; + border-width: var(--pf-v5-c-button--after--BorderWidth); + border-color: var(--pf-v5-c-button--after--BorderColor); + border-radius: var(--pf-v5-c-button--after--BorderRadius); + } + &.anchor { + text-decoration: none; + &::after { + pointer-events: all; + } + } +} + +#text { + display: inline; + position: relative; +} + +:host(:focus) { + --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--m-primary--focus--Color, + var(--pf-global--Color--light-100, #fff)); + --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--m-primary--focus--BackgroundColor, + var(--pf-global--primary-color--200, #004080)); + --pf-v5-c-button--after--BorderWidth: var(--pf-v5-c-button--focus--after--BorderWidth, + var(--pf-global--BorderWidth--md, 2px)); + /* DANGER */ + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-danger--focus--Color, + var(--pf-global--Color--light-100, #fff)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-danger--focus--BackgroundColor, + var(--pf-global--danger-color--200, #a30000)); + /* LINK */ + --pf-v5-c-button--m-link--Color: var(--pf-v5-c-button--m-link--focus--Color, + var(--pf-global--link--Color--hover, #004080)); + --pf-v5-c-button--m-link--BackgroundColor: var(--pf-v5-c-button--m-link--hover--BackgroundColor, transparent); + /* PLAIN */ + --pf-v5-c-button--m-plain--Color: var(--pf-v5-c-button--m-plain--focus--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-plain--BackgroundColor: var(--pf-v5-c-button--m-plain--focus--BackgroundColor, + transparent); +} + +:host(:hover) { + --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--m-primary--hover--Color, + var(--pf-global--Color--light-100, #fff)); + --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--m-primary--hover--BackgroundColor, + var(--pf-global--primary-color--200, #004080)); + --pf-v5-c-button--after--BorderWidth: var(--pf-v5-c-button--hover--after--BorderWidth, + var(--pf-global--BorderWidth--md, 2px)); + /* DANGER */ + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-danger--hover--Color, + var(--pf-global--Color--light-100, #fff)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-danger--hover--BackgroundColor, + var(--pf-global--danger-color--200, #a30000)); + /* LINK */ + --pf-v5-c-button--m-link--Color: var(--pf-v5-c-button--m-link--hover--Color, + var(--pf-global--link--Color--hover, #004080)); + --pf-v5-c-button--m-link--BackgroundColor: var(--pf-v5-c-button--m-link--hover--BackgroundColor, transparent); + /* PLAIN */ + --pf-v5-c-button--m-plain--Color: var(--pf-v5-c-button--m-plain--hover--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-plain--BackgroundColor: var(--pf-v5-c-button--m-plain--hover--BackgroundColor, + transparent); +} + +:host(:active) { + --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--m-primary--active--Color, + var(--pf-global--Color--light-100, #fff)); + --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--m-primary--active--BackgroundColor, + var(--pf-global--primary-color--200, #004080)); + --pf-v5-c-button--after--BorderWidth: var(--pf-v5-c-button--active--after--BorderWidth, + var(--pf-global--BorderWidth--md, 2px)); + /* DANGER */ + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-danger--active--Color, + var(--pf-global--Color--light-100, #fff)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-danger--active--BackgroundColor, + var(--pf-global--danger-color--200, #a30000)); + /* LINK */ + --pf-v5-c-button--m-link--Color: var(--pf-v5-c-button--m-link--active--Color, + var(--pf-global--link--Color--hover, #004080)); + --pf-v5-c-button--m-link--BackgroundColor: var(--pf-v5-c-button--m-link--active--BackgroundColor, transparent); +} + +.disabled, +:host(:disabled), +:host([danger]:disabled), +:host([link]:disabled) .link { + pointer-events: none; + cursor: default; +} + +[part=icon] { + --pf-v5-icon--size: 16px; + display: inline-flex; + align-items: center; + position: absolute; + & ::slotted(*) { + width: 16px; + max-width: 16px; + height: 16px; + max-height: 16px; + } +} + +.hasIcon [part=icon] { + cursor: pointer; +} + +.hasIcon #button { + position: absolute; + inset: 0; +} + +/****************************** + * * + * PLAIN * + * * + ******************************/ + +#button.plain { + --pf-v5-c-button--disabled--BackgroundColor: var(--pf-v5-c-button--m-plain--disabled--BackgroundColor, transparent); + --pf-v5-c-button--after--BorderWidth: 0 !important; + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-tertiary--after--BorderColor, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--disabled--Color: var(--pf-v5-c-button--m-plain--disabled--Color, + var(--pf-global--disabled-color--200, #d2d2d2)); + --_button-color: var(--pf-v5-c-button--m-plain--Color, + var(--pf-global--Color--200, #6a6e73)); + --_button-background-color: var(--pf-v5-c-button--m-plain--BackgroundColor, + transparent); + :host(:active) & { + --pf-v5-c-button--m-plain--Color: var(--pf-v5-c-button--m-plain--active--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-plain--BackgroundColor: var(--pf-v5-c-button--m-plain--active--BackgroundColor, + tranparent); + } + &:not(.hasIcon) [part=icon], + &.loading [part=icon] { + left: 16px; + } + & [part=icon] { + display: contents; + } + &.disabled, + &.link.disabled { + --_button-color: var(--pf-v5-c-button--disabled--Color, + var(--pf-v5-c-button--m-plain--disabled--Color, + var(--pf-global--disabled--color--200, #d2d2d2))); + } +} + +.hasIcon:not(.plain) [part=icon] { + position: relative; +} + +/****************************** + * * + * ICON POSITION RIGHT * + * * + ******************************/ + +:host([icon-position=right]) .loading [part=icon] { + order: 1; +} + +:host([icon-position=right]) #button.hasIcon { + padding-left: var(--pf-v5-c-button--PaddingLeft, var(--pf-global--spacer--md, 1rem)); + padding-right: calc(16px + 8px + var(--pf-v5-c-button--PaddingRight, var(--pf-global--spacer--md, 1rem))); +} + +/****************************** + * * + * WARNING * + * * + ******************************/ + +#button.warning { + --_button-color: var(--pf-v5-c-button--m-warning--Color, + var(--pf-global--Color--dark-100, #151515)); + --_button-background-color: var(--pf-v5-c-button--m-warning--BackgroundColor, + var(--pf-global--warning-color--100, #f0ab00)); + :host(:focus) & { + --pf-v5-c-button--m-warning--Color: var(--pf-v5-c-button--m-warning--focus--Color, + var(--pf-global--Color--dark-100, #151515)); + --pf-v5-c-button--m-warning--BackgroundColor: var(--pf-v5-c-button--m-warning--focus--BackgroundColor, + var(--pf-global--palette--gold-500, #c58c00)); + } + :host(:hover) & { + --pf-v5-c-button--m-warning--Color: var(--pf-v5-c-button--m-warning--hover--Color, + var(--pf-global--Color--dark-100, #151515)); + --pf-v5-c-button--m-warning--BackgroundColor: var(--pf-v5-c-button--m-warning--hover--BackgroundColor, + var(--pf-global--palette--gold-500, #c58c00)); + } + :host(:active) & { + --pf-v5-c-button--m-warning--Color: var(--pf-v5-c-button--m-warning--active--Color, + var(--pf-global--Color--dark-100, #151515)); + --pf-v5-c-button--m-warning--BackgroundColor: var(--pf-v5-c-button--m-warning--active--BackgroundColor, + var(--pf-global--palette--gold-500, #c58c00)); + } + &.disabled { + --_button-color: var(--pf-v5-c-button--disabled--Color, + var(--pf-global--disabled-color--100, #6a6e73)); + --_button-background-color: var(--pf-v5-c-button--disabled--BackgroundColor, + var(--pf-global--disabled-color--200, #d2d2d2)); + } +} + +/****************************** + * * + * LOADING * + * * + ******************************/ + +:host([loading]) #button { + position: relative; + display: flex; + align-items: center; +} + +#button.loading { + & [part=icon] { + display: inline-block; + z-index: 1; + position: absolute; + cursor: pointer; + top: var(--pf-v5-c-button__progress--Top, 50%); + left: var(--pf-v5-c-button__progress--Left, + var(--pf-global--spacer--md, 1rem)); + line-height: 1; + transform: translate( + var(--pf-v5-c-button__progress--TranslateX, 0), + var(--pf-v5-c-button__progress--TranslateY, -50%)); + margin-inline-end: var(--pf-v5-c-button__icon--m-start--MarginRight, + var(--pf-global--spacer--xs, 0.25rem)); + } + &.primary:not(.plain), + &.danger { + --pf-v5-c-spinner--Color: white; + } + &:not(.plain) { + padding-left: calc(12px + var(--pf-v5-c-button--PaddingLeft, var(--pf-global--spacer--md, 1rem))); + --pf-v5-c-button--PaddingRight: var(--pf-v5-c-button--m-in-progress--PaddingRight, + var(--pf-global--spacer--md, 1rem)); + --pf-v5-c-button--PaddingLeft: var(--pf-v5-c-button--m-in-progress--PaddingLeft, + calc( + var(--pf-global--spacer--md, 1rem) + var(--pf-v5-c-button__progress--width, + calc(var(--pf-global--icon--FontSize--md, 1.125rem) + var(--pf-global--spacer--sm, 0.5rem))) / 2)); + } + +} + +/****************************** + * * + * SECONDARY * + * * + ******************************/ + +#button.secondary { + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-secondary--m-danger--Color, + var(--pf-global--danger-color--100, #c9190b)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-secondary--m-danger--BackgroundColor, transparent); + --_button-color: var(--pf-v5-c-button--m-secondary--Color, + var(--pf-global--primary-color--100, #06c)); + --_button-background-color: var(--pf-v5-c-button--m-secondary--BackgroundColor, transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--after--BorderColor, + var(--pf-global--primary-color--100, #06c)); + :host(:focus) & { + --pf-v5-c-button--m-secondary--Color: var(--pf-v5-c-button--m-secondary--focus--Color, + var(--pf-global--primary-color--100, #06c)); + --pf-v5-c-button--m-secondary--BackgroundColor: var(--pf-v5-c-button--m-secondary--focus--BackgroundColor, transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--focus--after--BorderColor, + var(--pf-global--primary-color--100, #06c)); + /* DANGER */ + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-secondary--m-danger--focus--Color, + var(--pf-global--danger--color--200, #a30000)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-secondary--m-danger--focus--BackgroundColor, transparent); + } + :host(:hover) & { + --pf-v5-c-button--m-secondary--Color: var(--pf-v5-c-button--m-secondary--hover--Color, + var(--pf-global--primary-color--100, #06c)); + --pf-v5-c-button--m-secondary--BackgroundColor: var(--pf-v5-c-button--m-secondary--hover--BackgroundColor, + transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--hover--after--BorderColor, + var(--pf-global--primary-color--100, #06c)); + /* DANGER */ + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-secondary--m-danger--hover--Color, + var(--pf-global--danger--color--200, #a30000)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-secondary--m-danger--hover--BackgroundColor, transparent); + } + :host(:active) & { + --pf-v5-c-button--m-secondary--Color: var(--pf-v5-c-button--m-secondary--active--Color, + var(--pf-global--primary-color--100, #06c)); + --pf-v5-c-button--m-secondary--BackgroundColor: var(--pf-v5-c-button--m-secondary--active--BackgroundColor, transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--active--after--BorderColor, + var(--pf-global--primary-color--100, #06c)); + /* DANGER */ + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-secondary--m-danger--active--Color, + var(--pf-global--danger--color--200, #a30000)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-secondary--m-danger--active--BackgroundColor, transparent); + } + &.danger { + --_button-color: var(--pf-v5-c-button--m-secondary--m-danger--Color, + var(--pf-global--danger--color--100, #c9190b)); + --_button-background-color: var(--pf-v5-c-button--m-secondary--m-danger--BackgroundColor, transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--m-danger--after--BorderColor, + var(--pf-global--danger--color--100, #c9190b)); + :host(:focus) & { + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--m-danger--focus--after--BorderColor, + var(--pf-global--danger--color--100, #c9190b)); + } + :host(:hover) & { + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--m-danger--hover--after--BorderColor, + var(--pf-global--danger--color--100, #c9190b)); + } + :host(:active) & { + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-secondary--m-danger--active--after--BorderColor, + var(--pf-global--danger--color--100, #c9190b)); + } + } +} + +/****************************** + * * + * TERTIARY * + * * + ******************************/ + +#button.tertiary { + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-tertiary--after--BorderColor, + var(--pf-global--Color--100, #151515)); + --_button-color: var(--pf-v5-c-button--m-tertiary--Color, + var(--pf-global--Color--100, #151515)); + --_button-background-color: var(--pf-v5-c-button--m-tertiary--BackgroundColor, transparent); + :host(:focus) & { + --pf-v5-c-button--m-tertiary--Color: var(--pf-v5-c-button--m-tertiary--focus--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-tertiary--BackgroundColor: var(--pf-v5-c-button--m-tertiary--focus--BackgroundColor, + transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-tertiary--focus--after--BorderColor, + var(--pf-global--Color--100, #151515)); + } + :host(:hover) & { + --pf-v5-c-button--m-tertiary--Color: var(--pf-v5-c-button--m-tertiary--hover--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-tertiary--BackgroundColor: var(--pf-v5-c-button--m-tertiary--hover--BackgroundColor, transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-tertiary--hover--after--BorderColor, + var(--pf-global--Color--100, #151515)); + } + :host(:active) & { + --pf-v5-c-button--m-tertiary--Color: var(--pf-v5-c-button--m-tertiary--active--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-tertiary--BackgroundColor: var(--pf-v5-c-button--m-tertiary--active--BackgroundColor, transparent); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-tertiary--active--after--BorderColor, + var(--pf-global--Color--100, #151515)); + } +} + +/****************************** + * * + * CONTROL * + * * + ******************************/ + +#button.control { + --pf-v5-c-button--BorderRadius: var(--pf-v5-c-button--m-control--BorderRadius, 0); + --pf-v5-c-button--disabled--BackgroundColor: var(--pf-v5-c-button--m-control--disabled--BackgroundColor, + var(--pf-global--disabled-color--300, #f0f0f0)); + --pf-v5-c-button--after--BorderRadius: 0; + --pf-v5-c-button--after--BorderWidth: var(--pf-v5-c-button--m-control--after--BorderWidth, + var(--pf-global--BorderWidth--sm, 1px)); + --pf-v5-c-button--after--BorderColor: + var(--pf-v5-c-button--m-control--after--BorderTopColor, + var(--pf-global--BorderColor--300, #f0f0f0)) + var(--pf-v5-c-button--m-control--after--BorderRightColor, + var(--pf-global--BorderColor--300, #f0f0f0)) + var(--pf-v5-c-button--m-control--after--BorderBottomColor, + var(--pf-global--BorderColor--200, #8a8d90)) + var(--pf-v5-c-button--m-control--after--BorderLeftColor, + var(--pf-global--BorderColor--300, #f0f0f0)); + --_button-color: var(--pf-v5-c-button--m-control--Color, + var(--pf-global--Color--100, #151515)); + --_button-background-color: var(--pf-v5-c-button--m-control--BackgroundColor, + var(--pf-global--BackgroundColor--100, #fff)); + :host(:focus) & { + --pf-v5-c-button--m-control--Color: var(--pf-v5-c-button--m-control--focus--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-control--BackgroundColor: var(--pf-v5-c-button--m-control--focus--BackgroundColor, + var(--pf-global--BackgroundColor--100, #fff)); + --pf-v5-c-button--m-control--after--BorderBottomColor: var(--pf-v5-c-button--m-control--focus--after--BorderBottomColor, + var(--pf-global--active-color--100, #06c)); + &::after { + border-block-end-width: var(--pf-v5-c-button--m-control--focus--after--BorderBottomWidth, + var(--pf-global--BorderWidth--md, 2px)); + } + } + :host(:hover) & { + --pf-v5-c-button--m-control--Color: var(--pf-v5-c-button--m-control--hover--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-control--BackgroundColor: var(--pf-v5-c-button--m-control--hover--BackgroundColor, + var(--pf-global--BackgroundColor--100, #fff)); + --pf-v5-c-button--m-control--after--BorderBottomColor: var(--pf-v5-c-button--m-control--hover--after--BorderBottomColor, + var(--pf-global--active-color--100, #06c)); + &::after { + border-block-end-width: var(--pf-v5-c-button--m-control--hover--after--BorderBottomWidth, + var(--pf-global--BorderWidth--md, 2px)); + } + } + :host(:active) & { + --pf-v5-c-button--m-control--Color: var(--pf-v5-c-button--m-control--active--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-button--m-control--BackgroundColor: var(--pf-v5-c-button--m-control--active--BackgroundColor, + var(--pf-global--BackgroundColor--100, #fff)); + --pf-v5-c-button--m-control--after--BorderBottomColor: var(--pf-v5-c-button--m-control--active--after--BorderBottomColor, + var(--pf-global--active-color--100, #06c)); + &::after { + border-block-end-width: var(--pf-v5-c-button--m-control--active--after--BorderBottomWidth, + var(--pf-global--BorderWidth--md, 2px)); + } + } +} + +/****************************** + * * + * LINK * + * * + ******************************/ + +#button.link { + --pf-v5-c-button--disabled--BackgroundColor: var(--pf-v5-c-button--m-link--disabled--BackgroundColor, transparent); + --_button-color: var(--pf-v5-c-button--m-link--Color, var(--pf-global--link--Color, #06c)); + --_button-background-color: var(--pf-v5-c-button--m-link--BackgroundColor, + var(--pf-v5-c-button--m-link--BackgroundColor, transparent)); + &.inline { + :host(:hover) & { + text-decoration: var(--pf-v5-c-button--m-link--m-inline--hover--TextDecoration, + var(--pf-global--link--TextDecoration--hover, underline)); + } + } + &.danger { + --pf-v5-c-button--m-danger--Color: var(--pf-v5-c-button--m-link--m-danger--Color, + var(--pf-global--danger-color--100, #c9190b)); + --pf-v5-c-button--m-danger--BackgroundColor: var(--pf-v5-c-button--m-link--m-danger--BackgroundColor, transparent); + :host(:hover) & { + --pf-v5-c-button--m-link--m-danger--Color: var(--pf-v5-c-button--m-link--m-danger--hover--Color, + var(--pf-global--danger-color--200, #a30000)); + --pf-v5-c-button--m-link--m-danger--BackgroundColor: var(--pf-v5-c-button--m-link--m-danger--hover--BackgroundColor, transparent); + } + :host(:focus) & { + --pf-v5-c-button--m-link--m-danger--Color: var(--pf-v5-c-button--m-link--m-danger--focus--Color, + var(--pf-global--danger-color--200, #a30000)); + --pf-v5-c-button--m-link--m-danger--BackgroundColor: var(--pf-v5-c-button--m-link--m-danger--focus--BackgroundColor, transparent); + } + :host(:active) & { + --pf-v5-c-button--m-link--m-danger--Color: var(--pf-v5-c-button--m-link--m-danger--active--Color, + var(--pf-global--danger-color--200, #a30000)); + --pf-v5-c-button--m-link--m-danger--BackgroundColor: var(--pf-v5-c-button--m-link--m-danger--active--BackgroundColor, transparent); + } + } +} + +/****************************** + * * + * DISABLED * + * * + ******************************/ + +:host(:is(:disabled,[disabled])) { + pointer-events: none; + cursor: default; +} + +#button.disabled { + &:not(.plain) { + --_button-color: var(--pf-v5-c-button--disabled--Color, + var(--pf-global--disabled-color--100, #6a6e73)); + --_button-background-color: var(--pf-v5-c-button--disabled--BackgroundColor, + var(--pf-global--disabled-color--200, #d2d2d2)); + } + &::after { + border-color: var(--pf-v5-c-button--disabled--after--BorderColor, transparent); + } +} + +/****************************** + * * + * BLOCK * + * * + ******************************/ + +:host([block]) { + display: flex; + width: 100%; + justify-content: center; +} + +/****************************** + * * + * LARGE * + * * + ******************************/ + +:host([size="large"]) { + --pf-v5-c-button--PaddingTop: var(--pf-v5-c-button--m-display-lg--PaddingTop, + var(--pf-global--spacer--md, 1rem)); + --pf-v5-c-button--PaddingRight: var(--pf-v5-c-button--m-display-lg--PaddingRight, + var(--pf-global--spacer--xl, 2rem)); + --pf-v5-c-button--PaddingBottom: var(--pf-v5-c-button--m-display-lg--PaddingBottom, + var(--pf-global--spacer--md, 1rem)); + --pf-v5-c-button--PaddingLeft: var(--pf-v5-c-button--m-display-lg--PaddingLeft, + var(--pf-global--spacer--xl, 2rem)); + --pf-v5-c-button--FontWeight: var(--pf-v5-c-button--m-display-lg--FontWeight, + var(--pf-global--FontWeight--bold, 700)); +} + +/****************************** + * * + * SMALL * + * * + ******************************/ + +:host([size="small"]) { + --pf-v5-c-button--FontSize: var(--pf-v5-c-button--m-small--FontSize, + var(--pf-global--FontSize--md, 1rem)); +} + +/****************************** + * * + * DANGER * + * * + ******************************/ + +#button.danger { + --_button-color: var(--pf-v5-c-button--m-danger--Color, + var(--pf-global--Color--light-100, #fff)); + --_button-background-color: var(--pf-v5-c-button--m-danger--BackgroundColor, + var(--pf-global--danger-color--100, #c9190b)); +} + diff --git a/elements/pf-v5-button/pf-v5-button.ts b/elements/pf-v5-button/pf-v5-button.ts new file mode 100644 index 0000000000..14ff6b8127 --- /dev/null +++ b/elements/pf-v5-button/pf-v5-button.ts @@ -0,0 +1,238 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; +import '@patternfly/elements/pf-v5-spinner/pf-v5-spinner.js'; + +import tokensStyles from './pf-v5-button-tokens.css'; +import iconStyles from './pf-v5-button-icon.css'; + +import styles from './pf-v5-button.css'; + +export type ButtonVariant = ( + | 'primary' + | 'secondary' + | 'tertiary' + | 'control' + | 'link' +); + +/** + * A **button** is a box area or text that communicates and triggers user actions when + * clicked or selected. Buttons can be used to communicate and immediately trigger + * actions a user can take in an application, like submitting a form, canceling a + * process, or creating a new object. Buttons can also be used to take a user to a + * new location, like another page inside of a web application, or an external site + * such as help or documentation.. + * @summary Allows users to perform an action when triggered + * @alias Button + * @attr {string} [loading-label='loading'] - ARIA label for the loading indicator + */ +@customElement('pf-v5-button') +export class PfV5Button extends LitElement { + static readonly formAssociated = true; + + static readonly styles: CSSStyleSheet[] = [ + tokensStyles, + iconStyles, + styles, + ]; + + @property({ reflect: true }) type?: 'button' | 'submit' | 'reset'; + + /** Accessible name for the button, use when the button does not have slotted text */ + @property() label?: string; + + /** Form value for the button */ + @property() value?: string; + + /** Form element name for the button */ + @property() name?: string; + + /** Disables the button */ + @property({ reflect: true, type: Boolean }) disabled = false; + + /** Represents the state of a stateful button */ + @property({ type: Boolean, reflect: true }) loading = false; + + /** Changes the size of the button. */ + @property({ reflect: true }) size?: 'small' | 'large'; + + /** Not as urgent as danger */ + @property({ type: Boolean, reflect: true }) warning = false; + + /** + * Use danger buttons for actions a user can take that are potentially + * destructive or difficult/impossible to undo, like deleting or removing + * user data. + */ + @property({ type: Boolean, reflect: true }) danger = false; + + /** Applies plain styles */ + @property({ type: Boolean, reflect: true }) plain = false; + + /** + * Changes the style of the button. + * - Primary: Used for the most important call to action on a page. Try to + * limit primary buttons to one per page. + * - Secondary: Use secondary buttons for general actions on a page, that + * don’t require as much emphasis as primary button actions. For example, + * you can use secondary buttons where there are multiple actions, like in + * toolbars or data lists. + * - Tertiary: Tertiary buttons are flexible and can be used as needed. + */ + @property({ reflect: true }) variant: ButtonVariant = 'primary'; + + @property({ reflect: true, type: Boolean }) inline = false; + + @property({ reflect: true, type: Boolean }) block = false; + + /** Shorthand for the `icon` slot, the value is icon name */ + @property() icon?: string; + + /** Icon set for the `icon` property */ + @property({ attribute: 'icon-set' }) iconSet?: string; + + /** Store the URL Link */ + @property({ reflect: true }) href?: string; + + /** Redirecting the URL Link to new Tab */ + @property({ reflect: true }) target?: string; + + #internals = InternalsController.of(this, { role: this.variant === 'link' ? 'none' : 'button' }); + + #slots = new SlotController(this, 'icon', null); + + get #disabled() { + return this.disabled || this.#internals.formDisabled; + } + + override connectedCallback(): void { + super.connectedCallback(); + this.addEventListener('click', this.#onClick); + this.addEventListener('keydown', this.#onKeydown); + } + + protected override willUpdate(): void { + this.#internals.ariaLabel = this.label || null; + this.#internals.ariaDisabled = String(!!this.disabled); + const isLink = this.variant === 'link' && this.href; + if (isLink) { + this.removeAttribute('tabindex'); + this.#internals.role = 'none'; + } else { + this.tabIndex = 0; + this.#internals.role = 'button'; + } + } + + async formDisabledCallback(): Promise { + await this.updateComplete; + this.requestUpdate(); + } + + override render(): TemplateResult<1> { + const hasIcon = !!this.icon || !!this.loading || this.#slots.hasSlotted('icon'); + const { warning, variant, danger, loading, plain, inline, block, size, href, target } = this; + + const disabled = this.#disabled; + + const content = html` + + + + + + `; + + if (variant === 'link' && href) { + return html` + ${content}`; + } else { + return html` +
${content}
`; + } + } + + #onClick() { + if (!this.#disabled) { + switch (this.type) { + case 'reset': + return this.#internals.reset(); + default: + return this.#internals.submit(); + } + } + } + + #onKeydown(event: KeyboardEvent) { + switch (event.key) { + case ' ': + event.preventDefault(); + event.stopPropagation(); + if (this.dispatchEvent(new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window, + }))) { + this.#onClick(); + } + break; + case 'Enter': + if (this.dispatchEvent(new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window, + }))) { + this.#onClick(); + } + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-button': PfV5Button; + } +} diff --git a/elements/pf-button/test/pf-button.e2e.ts b/elements/pf-v5-button/test/pf-button.e2e.ts similarity index 95% rename from elements/pf-button/test/pf-button.e2e.ts rename to elements/pf-v5-button/test/pf-button.e2e.ts index 5072a7e6d7..f74a6ea00b 100644 --- a/elements/pf-button/test/pf-button.e2e.ts +++ b/elements/pf-v5-button/test/pf-button.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-button'; +const tagName = 'pf-v5-button'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-button/test/pf-button.spec.ts b/elements/pf-v5-button/test/pf-button.spec.ts similarity index 82% rename from elements/pf-button/test/pf-button.spec.ts rename to elements/pf-v5-button/test/pf-button.spec.ts index 034d2129d7..e8648456f5 100644 --- a/elements/pf-button/test/pf-button.spec.ts +++ b/elements/pf-v5-button/test/pf-button.spec.ts @@ -3,7 +3,7 @@ import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; import { sendKeys } from '@web/test-runner-commands'; import { clickElementAtCenter } from '@patternfly/pfe-tools/test/utils.js'; -import { PfButton } from '@patternfly/elements/pf-button/pf-button.js'; +import { PfV5Button } from '@patternfly/elements/pf-v5-button/pf-v5-button.js'; import '@patternfly/pfe-tools/test/stub-logger.js'; @@ -13,20 +13,20 @@ function press(key: string) { }; } -describe('', function() { +describe('', function() { it('imperatively instantiates', function() { - expect(document.createElement('pf-button')).to.be.an.instanceof(PfButton); + expect(document.createElement('pf-v5-button')).to.be.an.instanceof(PfV5Button); }); it('should upgrade', async function() { - const el = await createFixture(html`Button`); - expect(el, 'pf-button should be an instance of PfButton') - .to.be.an.instanceOf(customElements.get('pf-button')) + const el = await createFixture(html`Button`); + expect(el, 'pf-v5-button should be an instance of PfV5Button') + .to.be.an.instanceOf(customElements.get('pf-v5-button')) .and - .to.be.an.instanceOf(PfButton); + .to.be.an.instanceOf(PfV5Button); }); describe('in a fieldset', function() { - let element: PfButton; + let element: PfV5Button; let fieldset: HTMLFieldSetElement; let form: HTMLFormElement; let submitEvent: SubmitEvent; @@ -36,13 +36,13 @@ describe('', function() {
- OK + OK
`); fieldset = form.querySelector('fieldset')!; - element = form.querySelector('pf-button')!; + element = form.querySelector('pf-v5-button')!; form.querySelector('input')?.focus(); form.addEventListener('submit', function(event) { event.preventDefault(); @@ -68,7 +68,7 @@ describe('', function() { it('does focus the button', function() { expect(document.activeElement) .to - .be.an.instanceof(PfButton); + .be.an.instanceof(PfV5Button); }); describe('pressing Space', function() { @@ -100,7 +100,7 @@ describe('', function() { expect(document.activeElement) .to .not - .be.an.instanceof(PfButton); + .be.an.instanceof(PfV5Button); }); }); // this was a regression spotted by @brianferry @@ -124,7 +124,7 @@ describe('', function() { it('does focus the button', function() { expect(document.activeElement) .to - .be.an.instanceof(PfButton); + .be.an.instanceof(PfV5Button); }); }); }); diff --git a/elements/pf-v5-card/README.md b/elements/pf-v5-card/README.md new file mode 100644 index 0000000000..bd13b2d07a --- /dev/null +++ b/elements/pf-v5-card/README.md @@ -0,0 +1,34 @@ +# PatternFly Elements Card + +Read more about Card in the [PatternFly Elements Card documentation](https://patternflyelements.org/components/card) + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-card/pf-v5-card.js'; +``` + +## Usage + +```html + +

Card header

+

This is the pf-v5-card body.

+ Footer link +
+``` + diff --git a/elements/pf-card/demo/header-images-and-actions.html b/elements/pf-v5-card/demo/header-images-and-actions.html similarity index 75% rename from elements/pf-card/demo/header-images-and-actions.html rename to elements/pf-v5-card/demo/header-images-and-actions.html index 03cd21ee88..edaeea341e 100644 --- a/elements/pf-card/demo/header-images-and-actions.html +++ b/elements/pf-v5-card/demo/header-images-and-actions.html @@ -1,4 +1,4 @@ - + - - - - Action - Link - Disabled Action - Disabled Link + + + + Action + Link + Disabled Action + Disabled Link
- Separated Action - Separated Link -
-
+ Separated Action + Separated Link + +

Title

Body Footer -
+
diff --git a/elements/pf-v5-card/demo/index.html b/elements/pf-v5-card/demo/index.html new file mode 100644 index 0000000000..c79954f0a5 --- /dev/null +++ b/elements/pf-v5-card/demo/index.html @@ -0,0 +1,15 @@ + +

Header

+ Body + Footer +
+ + + + diff --git a/elements/pf-v5-card/demo/modifiers.html b/elements/pf-v5-card/demo/modifiers.html new file mode 100644 index 0000000000..a564146119 --- /dev/null +++ b/elements/pf-v5-card/demo/modifiers.html @@ -0,0 +1,107 @@ +
+
+ + + + + + + + + + + + + + + + + +
+ +
+ +

Lightest card

+

This is the lightest pf-v5-card and a link, and a visited link with border.

+ Try + Buy +
+
+
+ + + + diff --git a/elements/pf-v5-card/demo/ssr.html b/elements/pf-v5-card/demo/ssr.html new file mode 100644 index 0000000000..7a21fac5a2 --- /dev/null +++ b/elements/pf-v5-card/demo/ssr.html @@ -0,0 +1,50 @@ + +

Header

+

Body

+ Footer +
+ + +

Body

+
+ + +

Header

+
+ + +

Header

+

Body

+
+ + +

Header

+ Footer +
+ + +

Body

+ Footer +
+ + + Footer + + + diff --git a/elements/pf-v5-card/demo/title-inline-with-images-and-actions.html b/elements/pf-v5-card/demo/title-inline-with-images-and-actions.html new file mode 100644 index 0000000000..d3a7a0f355 --- /dev/null +++ b/elements/pf-v5-card/demo/title-inline-with-images-and-actions.html @@ -0,0 +1,22 @@ + +

This is a really really really really really really really really really really long header

+ + + + Action + Link + Disabled Action + Disabled Link +
+ Separated Action + Separated Link +
+
+ Body + Footer +
+ + diff --git a/elements/pf-card/docs/CHANGELOG.old.md b/elements/pf-v5-card/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-card/docs/CHANGELOG.old.md rename to elements/pf-v5-card/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-card/docs/pf-v5-card.md b/elements/pf-v5-card/docs/pf-v5-card.md new file mode 100644 index 0000000000..bd05d21ba5 --- /dev/null +++ b/elements/pf-v5-card/docs/pf-v5-card.md @@ -0,0 +1,147 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + + Cards are flexible surfaces used to group information in a small layout. They give small previews of information or provide secondary content in relation to the content it's near. Several cards can be used together to group related information. + + +

Header

+

This is the default card

+ Link in the footer +
+{% endrenderOverview %} + +{% band header="Usage" %} + + ### Basic cards + + ### Modifiers + {% htmlexample %} + +

Header

+

This is the compact card

+ Link in the footer +
+ {% endhtmlexample %} + + ### Large card + {% htmlexample %} + +

Large card

+

This is the large card

+ Link in the footer +
+ {% endhtmlexample %} + + ### Rounded card + {% htmlexample %} + +

Header

+

This is the rounded card

+ Link in the footer +
+ {% endhtmlexample %} + + ### Full Height card + {% htmlexample %} + +

Header

+

This is the full height card

+ Link in the footer +
+ {% endhtmlexample %} + + ### Plain card + {% htmlexample %} + +

Header

+

This is the plain card

+ Link in the footer +
+ {% endhtmlexample %} + + ### Header images and actions + You can include header images and actions in the `header` slot, along with a + title in the `title` slot. The following example includes an SVG image, and + also includes a kebab dropdown. + + {% htmlexample %} + + + + + + + + + Action + Link + Disabled Action + Disabled Link +
+ Separated Action + Separated Link +
+
+

Title

+ Body + Footer +
+ {% endhtmlexample %} + + ### Title inline with images and actions + Slotting the `

` into the `header` slot, instead of the `title` slot will + style it inline with any images or actions. + + {% htmlexample %} + +

This is a really really really really really really really really really really long header

+ + + + Action + Link + Disabled Action + Disabled Link +
+ Separated Action + Separated Link +
+
+ Body + Footer +
+ {% endhtmlexample %} + +{% endband %} + +{% renderSlots %} + All slots are optional. If the slot is not defined, the content will be added to the `body` region of the card. +{% endrenderSlots %} + +{% renderAttributes %} + + #### `overflow` (child element attribute) + Optionally allows an image or element to overflow the padding on the container. This property should be added to the direct child of the slot such as on an image tag; should be added to the element that you want to overflow the container. Accepts: `top`, `right`, `bottom`, `left`. + +{% endrenderAttributes %} + +{% renderProperties %}{% endrenderProperties %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} + + diff --git a/elements/pf-card/docs/screenshot.png b/elements/pf-v5-card/docs/screenshot.png similarity index 100% rename from elements/pf-card/docs/screenshot.png rename to elements/pf-v5-card/docs/screenshot.png diff --git a/elements/pf-v5-card/pf-v5-card.css b/elements/pf-v5-card/pf-v5-card.css new file mode 100644 index 0000000000..6632b3cb9b --- /dev/null +++ b/elements/pf-v5-card/pf-v5-card.css @@ -0,0 +1,226 @@ +:host { + /** BackgroundColor for the card */ + --pf-v5-c-card--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); + /** BoxShadow for the card */ + --pf-v5-c-card--BoxShadow: var(--pf-global--BoxShadow--sm, 0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06)); + --pf-v5-c-card--first-child--PaddingTop: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-card--child--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-card--child--PaddingBottom: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-card--child--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-card--c-divider--child--PaddingTop: var(--pf-global--spacer--lg, 1.5rem); + /** FontFamily for the card title */ + --pf-v5-c-card__title--FontFamily: var(--pf-global--FontFamily--heading--sans-serif, "RedHatDisplay", "Overpass", overpass, helvetica, arial, sans-serif); + /** FontSize for the card title */ + --pf-v5-c-card__title--FontSize: var(--pf-global--FontSize--md, 1rem); + /** FontWeight for the card title */ + --pf-v5-c-card__title--FontWeight: var(--pf-global--FontWeight--bold, 700); + --pf-v5-c-card__title--not--last-child--PaddingBottom: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-card__body--FontSize: var(--pf-global--FontSize--md, 1rem); + --pf-v5-c-card__footer--FontSize: var(--pf-global--FontSize--md, 1rem); + --pf-v5-c-card__actions--PaddingLeft: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-card__actions--child--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-card__header-toggle--MarginTop: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); + --pf-v5-c-card__header-toggle--MarginRight: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-card__header-toggle--MarginBottom: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); + --pf-v5-c-card__header-toggle--MarginLeft: calc(var(--pf-global--spacer--md, 1rem) * -1); + --pf-v5-c-card__header-toggle-icon--Transition: var(--pf-global--Transition, all 250ms cubic-bezier(0.42, 0, 0.58, 1)); + --pf-v5-c-card--m-expanded__header-toggle-icon--Rotate: 90deg; + --pf-v5-c-card--m-hoverable--hover--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); + --pf-v5-c-card--m-selectable--hover--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); + --pf-v5-c-card--m-selectable--focus--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); + --pf-v5-c-card--m-selectable--active--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); + --pf-v5-c-card--m-selectable--m-selected--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); + --pf-v5-c-card--m-selectable--m-selected--before--Height: var(--pf-global--BorderWidth--lg, 3px); + --pf-v5-c-card--m-selectable--m-selected--before--BackgroundColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-card--m-hoverable-raised--hover--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); + --pf-v5-c-card--m-hoverable-raised--hover--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); + --pf-v5-c-card--m-selectable-raised--before--Right: 0; + --pf-v5-c-card--m-selectable-raised--before--Bottom: 0; + --pf-v5-c-card--m-selectable-raised--before--Left: 0; + --pf-v5-c-card--m-flat--m-selectable-raised--before--Right: calc(-1 * var(--pf-v5-c-card--m-flat--BorderWidth)); + --pf-v5-c-card--m-flat--m-selectable-raised--before--Bottom: calc(-1 * var(--pf-v5-c-card--m-flat--BorderWidth)); + --pf-v5-c-card--m-flat--m-selectable-raised--before--Left: calc(-1 * var(--pf-v5-c-card--m-flat--BorderWidth)); + --pf-v5-c-card--m-selectable-raised--before--Height: var(--pf-global--BorderWidth--xl, 4px); + --pf-v5-c-card--m-selectable-raised--before--BackgroundColor: transparent; + --pf-v5-c-card--m-selectable-raised--before--Transition: none; + --pf-v5-c-card--m-selectable-raised--before--ScaleY: 1; + --pf-v5-c-card--m-selectable-raised--before--TranslateY: 0; + --pf-v5-c-card--m-selectable-raised--hover--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); + --pf-v5-c-card--m-selectable-raised--hover--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); + --pf-v5-c-card--m-selectable-raised--focus--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); + --pf-v5-c-card--m-selectable-raised--focus--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); + --pf-v5-c-card--m-selectable-raised--active--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); + --pf-v5-c-card--m-selectable-raised--active--before--BackgroundColor: var(--pf-global--active-color--400, #73bcf7); + --pf-v5-c-card--m-selectable-raised--m-selected-raised--before--BackgroundColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-card--m-selectable-raised--m-selected-raised--BoxShadow: var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)); + --pf-v5-c-card--m-selectable-raised--m-selected-raised--TranslateY--base: -0.5rem; + --pf-v5-c-card--m-selectable-raised--m-selected-raised--TranslateY: var(--pf-v5-c-card--m-selectable-raised--m-selected-raised--TranslateY--base); + --pf-v5-c-card--m-flat--m-selectable-raised--m-selected-raised--TranslateY: calc(var(--pf-v5-c-card--m-selectable-raised--m-selected-raised--TranslateY--base) + var(--pf-v5-c-card--m-flat--BorderWidth)); + --pf-v5-c-card--m-rounded--m-selectable-raised--m-selected-raised--TranslateY: calc(var(--pf-v5-c-card--m-selectable-raised--m-selected-raised--TranslateY--base) + var(--pf-v5-c-card--m-rounded--BorderRadius)); + --pf-v5-c-card--m-selectable-raised--m-selected-raised--ZIndex: var(--pf-global--ZIndex--xs, 100); + --pf-v5-c-card--m-selectable-raised--m-selected-raised--Transition: transform .25s linear, box-shadow .25s linear; + --pf-v5-c-card--m-selectable-raised--m-selected-raised--before--Transition: transform .25s linear; + --pf-v5-c-card--m-selectable-raised--m-selected-raised--before--TranslateY: calc(var(--pf-v5-c-card--m-selectable-raised--m-selected-raised--TranslateY) * -1); + --pf-v5-c-card--m-selectable-raised--m-selected-raised--before--ScaleY: 2; + --pf-v5-c-card--m-non-selectable-raised--BackgroundColor: var(--pf-global--BackgroundColor--light-200, #fafafa); + --pf-v5-c-card--m-non-selectable-raised--before--BackgroundColor: var(--pf-global--disabled-color--200, #d2d2d2); + --pf-v5-c-card--m-non-selectable-raised--before--ScaleY: 2; + --pf-v5-c-card--m-flat--m-non-selectable-raised--before--BorderColor: var(--pf-global--disabled-color--200, #d2d2d2); + /** FontSize for the card body in compact size */ + --pf-v5-c-card--m-compact__body--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** FontSize for the card footer in compact size */ + --pf-v5-c-card--m-compact__footer--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** PaddingTop for the first child in compact size */ + --pf-v5-c-card--m-compact--first-child--PaddingTop: var(--pf-global--spacer--md, 1rem); + /** PaddingRight for children in compact size */ + --pf-v5-c-card--m-compact--child--PaddingRight: var(--pf-global--spacer--md, 1rem); + /** PaddingBottom for children in compact size */ + --pf-v5-c-card--m-compact--child--PaddingBottom: var(--pf-global--spacer--md, 1rem); + /** PaddingLeft for children in compact size */ + --pf-v5-c-card--m-compact--child--PaddingLeft: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-card--m-compact--c-divider--child--PaddingTop: var(--pf-global--spacer--md, 1rem); + /** PaddingBottom for the title when not last child in compact size */ + --pf-v5-c-card--m-compact__title--not--last-child--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); + /** FontSize for the title in large size */ + --pf-v5-c-card--m-display-lg__title--FontSize: var(--pf-global--FontSize--xl, 1.25rem); + /** PaddingTop for the first child in large size */ + --pf-v5-c-card--m-display-lg--first-child--PaddingTop: var(--pf-global--spacer--xl, 2rem); + /** PaddingRight for children in large size */ + --pf-v5-c-card--m-display-lg--child--PaddingRight: var(--pf-global--spacer--xl, 2rem); + /** PaddingBottom for children in large size */ + --pf-v5-c-card--m-display-lg--child--PaddingBottom: var(--pf-global--spacer--xl, 2rem); + /** PaddingLeft for children in large size */ + --pf-v5-c-card--m-display-lg--child--PaddingLeft: var(--pf-global--spacer--xl, 2rem); + --pf-v5-c-card--m-display-lg--c-divider--child--PaddingTop: var(--pf-global--spacer--xl, 2rem); + /** PaddingBottom for the title when not last child in large size */ + --pf-v5-c-card--m-display-lg__title--not--last-child--PaddingBottom: var(--pf-global--spacer--lg, 1.5rem); + /** BorderWidth for the flat variant */ + --pf-v5-c-card--m-flat--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); + /** BorderColor for the flat variant */ + --pf-v5-c-card--m-flat--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); + /** BorderRadius for the rounded variant */ + --pf-v5-c-card--m-rounded--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + /** Height for the full height variant */ + --pf-v5-c-card--m-full-height--Height: 100%; + /** BoxShadow for the plain variant */ + --pf-v5-c-card--m-plain--BoxShadow: none; + /** BackgroundColor for the plain variant */ + --pf-v5-c-card--m-plain--BackgroundColor: transparent; + --pf-v5-c-card__header--m-toggle-right--toggle--MarginRight: calc(var(--pf-global--spacer--form-element, 0.375rem) * -1); + --pf-v5-c-card__header--m-toggle-right--toggle--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-card__header--m-toggle-right--actions--MarginRight: 0; + --pf-v5-c-card__input--focus--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-card__input--focus--BorderColor: var(--pf-global--primary-color--100, #06c); + display: flex; + flex-direction: column; + background-color: var(--pf-v5-c-card--BackgroundColor); + box-shadow: var(--pf-v5-c-card--BoxShadow); +} + +[hidden], +.empty { + display: none !important; +} + +header { + padding-block-start: var(--pf-v5-c-card--first-child--PaddingTop); + padding-block-end: var(--pf-v5-c-card__title--not--last-child--PaddingBottom); + display: flex; + flex-flow: row wrap; + align-items: center; +} + +header ::slotted(*) { + margin-block: 0 !important; + + font-family: var(--pf-v5-c-card__title--FontFamily) !important; + font-size: var(--pf-v5-c-card__title--FontSize) !important; + font-weight: var(--pf-v5-c-card__title--FontWeight) !important; +} + +header ::slotted(pf-v5-dropdown) { + margin-inline-start: auto; +} + +article { + position: relative; + height: 100%; + display: flex; + flex-direction: column; +} + +[part="header"], +[part="body"], +[part="footer"] { + padding-inline-start: var(--pf-v5-c-card--child--PaddingLeft); + padding-inline-end: var(--pf-v5-c-card--child--PaddingRight); + padding-block-end: var(--pf-v5-c-card--child--PaddingBottom); +} + +#title { + display: block; + flex: 1 0 100%; + padding-block-start: var(--pf-v5-c-card__title--not--last-child--PaddingBottom); +} + +[part="body"] { + font-size: var(--pf-v5-c-card__body--FontSize); + flex: var(--pf-v5-c-card__body--FullHeight--Flex); +} + +[part="body"] ::slotted(:not([slot]):first-of-type) { + margin-block-start: 0 !important; +} + +[part="body"] ::slotted(:not([slot]):last-of-type) { + margin-block-end: 0 !important; +} + +[part="footer"] { + margin-block-start: auto; + display: flex; + gap: 0.5em; + inset-block-end: 0; + font-size: var(--pf-v5-c-card__footer--FontSize); +} + +:host([size="compact"]) { + --pf-v5-c-card__body--FontSize: var(--pf-v5-c-card--m-compact__body--FontSize); + --pf-v5-c-card__footer--FontSize: var(--pf-v5-c-card--m-compact__footer--FontSize); + --pf-v5-c-card--first-child--PaddingTop: var(--pf-v5-c-card--m-compact--first-child--PaddingTop); + --pf-v5-c-card--child--PaddingRight: var(--pf-v5-c-card--m-compact--child--PaddingRight); + --pf-v5-c-card--child--PaddingBottom: var(--pf-v5-c-card--m-compact--child--PaddingBottom); + --pf-v5-c-card--child--PaddingLeft: var(--pf-v5-c-card--m-compact--child--PaddingLeft); + --pf-v5-c-card--c-divider--child--PaddingTop: var(--pf-v5-c-card--m-compact--c-divider--child--PaddingTop); + --pf-v5-c-card__title--not--last-child--PaddingBottom: var(--pf-v5-c-card--m-compact__title--not--last-child--PaddingBottom); +} + +:host([size="large"]) { + --pf-v5-c-card__title--FontSize: var(--pf-v5-c-card--m-display-lg__title--FontSize); + --pf-v5-c-card--first-child--PaddingTop: var(--pf-v5-c-card--m-display-lg--first-child--PaddingTop); + --pf-v5-c-card--child--PaddingRight: var(--pf-v5-c-card--m-display-lg--child--PaddingRight); + --pf-v5-c-card--child--PaddingBottom: var(--pf-v5-c-card--m-display-lg--child--PaddingBottom); + --pf-v5-c-card--child--PaddingLeft: var(--pf-v5-c-card--m-display-lg--child--PaddingLeft); + --pf-v5-c-card--c-divider--child--PaddingTop: var(--pf-v5-c-card--m-display-lg--c-divider--child--PaddingTop); + --pf-v5-c-card__title--not--last-child--PaddingBottom: var(--pf-v5-c-card--m-display-lg__title--not--last-child--PaddingBottom); +} + +:host([flat]) { + --pf-v5-c-card--BoxShadow: none; + border: var(--pf-v5-c-card--m-flat--BorderWidth) solid var(--pf-v5-c-card--m-flat--BorderColor); +} + +:host([plain]) { + --pf-v5-c-card--BoxShadow: var(--pf-v5-c-card--m-plain--BoxShadow); + --pf-v5-c-card--BackgroundColor: var(--pf-v5-c-card--m-plain--BackgroundColor); +} + +:host([rounded]) { + border-radius: var(--pf-v5-c-card--m-rounded--BorderRadius); +} + +:host([full-height]) { + height: var(--pf-v5-c-card--m-full-height--Height); + --pf-v5-c-card__body--FullHeight--Flex: 1 1 auto; +} + diff --git a/elements/pf-v5-card/pf-v5-card.ts b/elements/pf-v5-card/pf-v5-card.ts new file mode 100644 index 0000000000..40681f2898 --- /dev/null +++ b/elements/pf-v5-card/pf-v5-card.ts @@ -0,0 +1,96 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import style from './pf-v5-card.css'; + +/** + * A **card** is a square or rectangular container that can contain any kind of content. + * Cards symbolize units of information, and each one acts as an entry point for + * users to access more details. For example, in dashboards and catalog views, cards + * function as a preview of a detailed page. Cards may also be used in data displays + * like card views, or for positioning content on a page. + * @summary Gives a preview of information in a small layout + * @alias Card + */ +@customElement('pf-v5-card') +export class PfV5Card extends LitElement { + static readonly styles: CSSStyleSheet[] = [style]; + + /** + * Optionally provide a size for the card and the card contents. + * The default is set to `undefined` and provides default styles. + * Compact provides styles which decreases the padding between the sections. + * Large provides styles which increases the padding between the sections and the font size for the title, header, and footer. + */ + @property({ reflect: true }) size?: 'compact' | 'large'; + + /** + * Optionally apply a border radius for the drop shadow and/or border. + */ + @property({ type: Boolean, reflect: true }) rounded = false; + + /** + * Optionally allow the card to take up the full height of the parent element. + */ + @property({ type: Boolean, reflect: true, attribute: 'full-height' }) fullHeight = false; + + /** + * Optionally remove the border on the card container. + */ + @property({ type: Boolean, reflect: true }) plain = false; + + #slots = new SlotController(this, 'header', 'title', null, 'footer'); + + render(): TemplateResult<1> { + return html` +
+ + + +
+ + +
+ +
+ + +
+
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-card': PfV5Card; + } +} diff --git a/elements/pf-card/test/pf-card.e2e.ts b/elements/pf-v5-card/test/pf-card.e2e.ts similarity index 77% rename from elements/pf-card/test/pf-card.e2e.ts rename to elements/pf-v5-card/test/pf-card.e2e.ts index 27ea980470..9e4939794c 100644 --- a/elements/pf-card/test/pf-card.e2e.ts +++ b/elements/pf-v5-card/test/pf-card.e2e.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-card'; +const tagName = 'pf-v5-card'; const html = String.raw; @@ -31,16 +31,16 @@ test.describe(tagName, () => { browser, importSpecifiers: [`@patternfly/elements/${tagName}/${tagName}.js`], demoContent: html` -

Header

Body Footer -
+ `, }); - await fixture.updateCompleteFor('pf-card'); - await expect(fixture.page.locator('pf-card #title')).toHaveAttribute('hidden'); - await expect(fixture.page.locator('pf-card #header')).not.toHaveAttribute('hidden'); + await fixture.updateCompleteFor('pf-v5-card'); + await expect(fixture.page.locator('pf-v5-card #title')).toHaveAttribute('hidden'); + await expect(fixture.page.locator('pf-v5-card #header')).not.toHaveAttribute('hidden'); }); }); diff --git a/elements/pf-card/test/pf-card.spec.ts b/elements/pf-v5-card/test/pf-card.spec.ts similarity index 90% rename from elements/pf-card/test/pf-card.spec.ts rename to elements/pf-v5-card/test/pf-card.spec.ts index 1c21441732..05293b23ed 100644 --- a/elements/pf-card/test/pf-card.spec.ts +++ b/elements/pf-v5-card/test/pf-card.spec.ts @@ -2,31 +2,31 @@ import { expect, html, aTimeout, nextFrame } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; import '@patternfly/pfe-tools/test/stub-logger.js'; -import { PfCard } from '@patternfly/elements/pf-card/pf-card.js'; +import { PfV5Card } from '@patternfly/elements/pf-v5-card/pf-v5-card.js'; -describe('', function() { +describe('', function() { it('imperatively instantiates', function() { - expect(document.createElement('pf-card')).to.be.an.instanceof(PfCard); + expect(document.createElement('pf-v5-card')).to.be.an.instanceof(PfV5Card); }); it('should upgrade', async function() { - expect(await createFixture(html``)) - .to.be.an.instanceof(customElements.get('pf-card')) + expect(await createFixture(html``)) + .to.be.an.instanceof(customElements.get('pf-v5-card')) .and - .to.be.an.instanceof(PfCard); + .to.be.an.instanceof(PfV5Card); }); describe('with header and footer content', function() { - let element: PfCard; + let element: PfV5Card; let origHeight: number; beforeEach(async function() { - element = await createFixture(html` - + element = await createFixture(html` +

Card 1

-

This is pf-card.

+

This is pf-v5-card.

Text in footer -
`); +
`); }); beforeEach(function() { @@ -157,10 +157,10 @@ describe('', function() { }); it(`should render a header and footer when content for those slots are added dynamically`, async function() { - const element = await createFixture(html` - + const element = await createFixture(html` + This is the card - `); + `); const header = document.createElement('h2'); header.setAttribute('slot', 'header'); diff --git a/elements/pf-v5-chip/README.md b/elements/pf-v5-chip/README.md new file mode 100644 index 0000000000..35e49ae23e --- /dev/null +++ b/elements/pf-v5-chip/README.md @@ -0,0 +1,20 @@ +# Chip + +A **chip** is used to communicate a value or a set of attribute-value pairs within workflows that involve filtering a set of objects. + +## Usage + +Describe how best to use this web component along with best practices. + +```html +Chip 1 +``` + +```html + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + +``` diff --git a/elements/pf-v5-chip/demo/chip-group-closeable.html b/elements/pf-v5-chip/demo/chip-group-closeable.html new file mode 100644 index 0000000000..da6b407a1d --- /dev/null +++ b/elements/pf-v5-chip/demo/chip-group-closeable.html @@ -0,0 +1,10 @@ + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + + diff --git a/elements/pf-v5-chip/demo/chip-group-open.html b/elements/pf-v5-chip/demo/chip-group-open.html new file mode 100644 index 0000000000..f74e1e5329 --- /dev/null +++ b/elements/pf-v5-chip/demo/chip-group-open.html @@ -0,0 +1,10 @@ + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + + diff --git a/elements/pf-v5-chip/demo/chip-group-with-accessible-label.html b/elements/pf-v5-chip/demo/chip-group-with-accessible-label.html new file mode 100644 index 0000000000..0c1a851f54 --- /dev/null +++ b/elements/pf-v5-chip/demo/chip-group-with-accessible-label.html @@ -0,0 +1,10 @@ + +

Chip group, with off-screen category label (for screen readers)

+ + Chip 1 + Chip 2 + Chip 3 + Chip 4 + \ No newline at end of file diff --git a/elements/pf-v5-chip/demo/chip-group-with-custom-number-of-chips.html b/elements/pf-v5-chip/demo/chip-group-with-custom-number-of-chips.html new file mode 100644 index 0000000000..61948569ee --- /dev/null +++ b/elements/pf-v5-chip/demo/chip-group-with-custom-number-of-chips.html @@ -0,0 +1,10 @@ + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + + diff --git a/elements/pf-v5-chip/demo/chip-group-with-visible-category-name.html b/elements/pf-v5-chip/demo/chip-group-with-visible-category-name.html new file mode 100644 index 0000000000..c88d01ac2a --- /dev/null +++ b/elements/pf-v5-chip/demo/chip-group-with-visible-category-name.html @@ -0,0 +1,11 @@ + + Category three + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + + diff --git a/elements/pf-v5-chip/demo/chip-group.html b/elements/pf-v5-chip/demo/chip-group.html new file mode 100644 index 0000000000..64ed56cf25 --- /dev/null +++ b/elements/pf-v5-chip/demo/chip-group.html @@ -0,0 +1,10 @@ + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + + diff --git a/elements/pf-v5-chip/demo/index.html b/elements/pf-v5-chip/demo/index.html new file mode 100644 index 0000000000..9b6cbeda67 --- /dev/null +++ b/elements/pf-v5-chip/demo/index.html @@ -0,0 +1,6 @@ +Chip 1 + + + diff --git a/elements/pf-v5-chip/demo/long-chip-with-tooltip.html b/elements/pf-v5-chip/demo/long-chip-with-tooltip.html new file mode 100644 index 0000000000..03ebabe000 --- /dev/null +++ b/elements/pf-v5-chip/demo/long-chip-with-tooltip.html @@ -0,0 +1,21 @@ + + Really long chip that goes on and on + Really long chip that goes on and on + + + + + + diff --git a/elements/pf-v5-chip/demo/overflow.html b/elements/pf-v5-chip/demo/overflow.html new file mode 100644 index 0000000000..9726df2337 --- /dev/null +++ b/elements/pf-v5-chip/demo/overflow.html @@ -0,0 +1,6 @@ +Overflow chip + + + diff --git a/elements/pf-v5-chip/demo/read-only.html b/elements/pf-v5-chip/demo/read-only.html new file mode 100644 index 0000000000..09c7df12b5 --- /dev/null +++ b/elements/pf-v5-chip/demo/read-only.html @@ -0,0 +1,8 @@ +Read-only chip + + + + + diff --git a/elements/pf-v5-chip/demo/with-badge.html b/elements/pf-v5-chip/demo/with-badge.html new file mode 100644 index 0000000000..839480a861 --- /dev/null +++ b/elements/pf-v5-chip/demo/with-badge.html @@ -0,0 +1,8 @@ +Chip 7 + + + + diff --git a/elements/pf-v5-chip/docs/pf-v5-chip.md b/elements/pf-v5-chip/docs/pf-v5-chip.md new file mode 100644 index 0000000000..68c02f3353 --- /dev/null +++ b/elements/pf-v5-chip/docs/pf-v5-chip.md @@ -0,0 +1,139 @@ + + +{% renderOverview %} + Chip 1 +{% endrenderOverview %} + +{% band header="Usage" %} + +### Default + +{% htmlexample %} + Chip 1 + Chip 7 +{% endhtmlexample %} + +### Chip variants + +Chips can be removable or read-only. The Overflow chip is a special chip that is used to expand or collapse the content of a chip group. + +#### Read-only + +{% htmlexample %} + Read-only chip +{% endhtmlexample %} + +#### Overflow chip + +{% htmlexample %} + Overflow-chip +{% endhtmlexample %} + +#### Max-width + +{% htmlexample %} + + Really long chip that goes on and on + Really long chip that goes on and on + +{% endhtmlexample %} + +### Chip group + +A **chip group** is a collection of chips that can be grouped by category and used to represent one or more values assigned to a single attribute. When the value of `numChips` is exceeded, additional chips will be hidden using an overflow chip. + +Chip groups are typically used in filter and selection use cases to indicate to the user what selections they have made. They separate selections by attribute, for added clarity. An OR relationship is implied between values in the group. Chip groups also give users the ability to either delete an entire chip group at once using the group X, or delete individual chips at a time. + +Chip groups are useful to express complex filters to a data set, for example. + +{% htmlexample %} + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + +{% endhtmlexample %} + +#### Chip group with categories + +{% htmlexample %} + + Category one + Chip 1 + Chip 2 + Chip 3 + Chip 4 + +

+ + Category two + Chip 5 + Chip 6 + Chip 7 + Chip 8 + +{% endhtmlexample %} + +#### Chip group with removable categories + +{% htmlexample %} + + Category three + Chip 1 + Chip 2 + Chip 3 + Chip 4 + +{% endhtmlexample %} + +{% endband %} + +{% band header="Accessibility" %} + +The select uses the [Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) recommendations from the WAI ARIA [Authoring Best Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg). + +#### Chips + +When focus is on an individual chip that is not `readonly`, the following keyboard interactions apply: + +| Key | Function | +| ---------------------- | ----------------------------------------------------------------------------------- | +| Enter | On an overflow chip, shows/hides additional chips. Otherwise removes a chip. | +| Space | On an overflow chip, shows/hides additional chips. Otherwise removes a chip. | + +#### Chip groups + +Chip groups use the [APG's Roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) recommendation. When focus in a chip group, the following keyboard interactions apply: + +| Key | Function | +| ---------------------- | ----------------------------------------------------------------------------------- | +| Enter | When focus is on a group's remove botton, removes the group. | +| Space | When focus is on a group's remove botton, removes the group. | +| Up Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | +| Down Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | +| Left Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | +| Right Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | +| Home | Moves focus to the first item in the current group. | + +{% endband %} + +{% renderSlots for="pf-v5-chip", header="Slots on `pf-v5-chip`" %}{% endrenderSlots %} +{% renderAttributes for="pf-v5-chip", header="Attributes on `pf-v5-chip`" %}{% endrenderAttributes %} +{% renderMethods for="pf-v5-chip", header="Methods on `pf-v5-chip`" %}{% endrenderMethods %} +{% renderEvents for="pf-v5-chip", header="Events on `pf-v5-chip`" %}{% endrenderEvents %} +{% renderCssCustomProperties for="pf-v5-chip", header="CSS Custom Properties on `pf-v5-chip`" %}{% endrenderCssCustomProperties %} +{% renderCssParts for="pf-v5-chip", header="CSS Parts on `pf-v5-chip`" %}{% endrenderCssParts %} + +{% renderSlots for="pf-v5-chip-group", header="Slots on `pf-v5-chip-group`" %}{% endrenderSlots %} +{% renderAttributes for="pf-v5-chip-group", header="Attributes on `pf-v5-chip-group`" %}{% endrenderAttributes %} +{% renderMethods for="pf-v5-chip-group", header="Methods on `pf-v5-chip-group`" %}{% endrenderMethods %} +{% renderEvents for="pf-v5-chi-group", header="Events on `pf-v5-chip-group`" %}{% endrenderEvents %} +{% renderCssCustomProperties for="pf-v5-chip-group", header="CSS Custom Properties on `pf-v5-chip`" %}{% endrenderCssCustomProperties %} +{% renderCssParts for="pf-v5-chip-group", header="CSS Parts on `pf-v5-chip-group`" %}{% endrenderCssParts %} diff --git a/elements/pf-chip/docs/screenshot.png b/elements/pf-v5-chip/docs/screenshot.png similarity index 100% rename from elements/pf-chip/docs/screenshot.png rename to elements/pf-v5-chip/docs/screenshot.png diff --git a/elements/pf-v5-chip/pf-v5-chip-group.css b/elements/pf-v5-chip/pf-v5-chip-group.css new file mode 100644 index 0000000000..d05d8ca442 --- /dev/null +++ b/elements/pf-v5-chip/pf-v5-chip-group.css @@ -0,0 +1,109 @@ +:host { + /** MarginBottom for the chip group list */ + --pf-v5-c-chip-group__list--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + /** MarginRight for the chip group list */ + --pf-v5-c-chip-group__list--MarginRight: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + /** PaddingTop for the chip group category */ + --pf-v5-c-chip-group--m-category--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); + /** PaddingRight for the chip group category */ + --pf-v5-c-chip-group--m-category--PaddingRight: var(--pf-global--spacer--xs, 0.25rem); + /** PaddingBottom for the chip group category */ + --pf-v5-c-chip-group--m-category--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + /** PaddingLeft for the chip group category */ + --pf-v5-c-chip-group--m-category--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + /** BorderRadius for the chip group category */ + --pf-v5-c-chip-group--m-category--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + /** BackgroundColor for the chip group category */ + --pf-v5-c-chip-group--m-category--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); + /** MarginRight for the chip group label */ + --pf-v5-c-chip-group__label--MarginRight: var(--pf-global--spacer--sm, 0.5rem); + /** FontSize for the chip group label */ + --pf-v5-c-chip-group__label--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** MaxWidth for the chip group label */ + --pf-v5-c-chip-group__label--MaxWidth: 18ch; + /** MarginTop for the chip group close button */ + --pf-v5-c-chip-group__close--MarginTop: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + /** MarginBottom for the chip group close button */ + --pf-v5-c-chip-group__close--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + /** MarginRight for chip group list items */ + --pf-v5-c-chip-group__list-item--MarginRight: var(--pf-global--spacer--xs, 0.25rem); + /** MarginBottom for chip group list items */ + --pf-v5-c-chip-group__list-item--MarginBottom: var(--pf-global--spacer--xs, 0.25rem); + display: inline-flex; + flex-wrap: wrap; + align-items: center; + min-width: 0; + max-width: 100%; + color: var(--pf-global--Color--100, #151515); +} + +[hidden], +.empty, +::slotted([overflow-hidden]) { + display: none !important; +} + +#outer { + display: flex; + flex: 1; + flex-wrap: wrap; + align-items: baseline; + min-width: 0; + display: inline-flex; + column-gap: var(--pf-global--spacer--xs, 0.25rem); + align-items: center; + border-radius: var(--pf-global--BorderRadius--sm, 3px); + padding: 0 !important; +} + +#outer.has-category { + padding: var(--pf-global--spacer--xs, 0.25rem) var(--pf-global--spacer--sm, 0.5rem) !important; + background-color: var(--pf-v5-theme--color--surface--lighter, #f0f0f0); +} + +#chips { + margin-right: var(--pf-v5-c-chip-group__list--MarginRight); + margin-bottom: var(--pf-v5-c-chip-group__list--MarginBottom); + font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); + font-size: var(--pf-global--FontSize--sm, 14px); + font-weight: var(--pf-global--FontWeight--normal, 400); + line-height: 1.6; +} + +#chips ::slotted(pf-v5-chip) { + display: inline-flex; + min-width: 0; + margin-right: var(--pf-v5-c-chip-group__list-item--MarginRight); + margin-bottom: var(--pf-v5-c-chip-group__list-item--MarginBottom); +} + +#close-button { + --pf-v5-icon--size: 16px; + margin: var(--pf-v5-c-chip__c-button--MarginTop) + var(--pf-v5-c-chip__c-button--MarginRight) + var(--pf-v5-c-chip__c-button--MarginBottom) + var(--pf-v5-c-chip__c-button--MarginLeft); + padding: var(--pf-v5-c-chip__c-button--PaddingTop) + var(--pf-v5-c-chip__c-button--PaddingRight) + var(--pf-v5-c-chip__c-button--PaddingBottom) + var(--pf-v5-c-chip__c-button--PaddingLeft); + inset-block-start: 0.125em; +} + +svg { + width: var(--pf-global--FontSize--sm, 14px); + height: var(--pf-global--FontSize--sm, 14px); +} + +.visually-hidden { + border: 0; + clip: rect(0, 0, 0, 0); + block-size: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + inline-size: 1px; +} + diff --git a/elements/pf-v5-chip/pf-v5-chip-group.ts b/elements/pf-v5-chip/pf-v5-chip-group.ts new file mode 100644 index 0000000000..239f31b57d --- /dev/null +++ b/elements/pf-v5-chip/pf-v5-chip-group.ts @@ -0,0 +1,245 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { query } from 'lit/decorators/query.js'; +import { queryAssignedNodes } from 'lit/decorators/query-assigned-nodes.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { observes } from '@patternfly/pfe-core/decorators/observes.js'; +import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; + +import { PfV5Chip, PfV5ChipRemoveEvent } from './pf-v5-chip.js'; + +import styles from './pf-v5-chip-group.css'; +import shared from './pf-v5-chip-shared.css'; + +export class PfV5ChipGroupExpandEvent extends Event { + constructor() { + super('expand', { bubbles: true, cancelable: true }); + } +} + +export class PfV5ChipGroupRemoveEvent extends Event { + constructor() { + super('remove', { bubbles: true, cancelable: true }); + } +} + +/** + * `${` + * **WS** (_>= 0x_) + * `remaining` + * **WS** (_>= 0x_) + * `}` + */ +const REMAINING_RE = /\$\{\s*remaining\s*\}/g; + +/** + * A **chip group** is a collection of chips that can be grouped by category and used to represent one or more values assigned to a single attribute. When value of numChips is exceeded, additional chips will be hidden using an overflow chip. + * @fires expand - Fires when chip group is expanded to show all chips + * @fires remove - Fires when chip group is closed/removed + * @slot category-name + * Category name text for chip group category. If this prop is supplied chip group with have a label and category styling applied + * @slot - `` elements. + */ +@customElement('pf-v5-chip-group') +export class PfV5ChipGroup extends LitElement { + static readonly styles: CSSStyleSheet[] = [shared, styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** + * Accessible label for chip group that does not have a category name + */ + @property({ attribute: 'accessible-label', type: String }) accessibleLabel = ''; + + /** + * Accessible label for close button + */ + @property({ attribute: 'accessible-close-label', type: String }) accessibleCloseLabel = 'Close'; + + /** + * Customizeable "more" template string. Use variable "${remaining}" for overflow chip count. + */ + @property({ attribute: 'collapsed-text', type: String }) collapsedText = '${remaining} more'; + + /** + * Customizable "show less" text string. + */ + @property({ attribute: 'expanded-text', type: String }) expandedText = 'show less'; + + /** + * Set number of chips to show before overflow + */ + @property({ attribute: 'num-chips', type: Number }) numChips = 3; + + /** + * Flag indicating if overflow chips are visible + */ + @property({ reflect: true, type: Boolean }) open = false; + + /** + * Flag if chip group can be closed + */ + @property({ reflect: true, type: Boolean }) closeable = false; + + @query('#overflow') private _overflowChip?: PfV5Chip; + + @query('#close-button') private _button?: HTMLButtonElement; + + @queryAssignedNodes({ slot: 'category-name', flatten: true }) private _categorySlotted?: Node[]; + + get #chips(): NodeListOf { + return this.querySelectorAll('pf-v5-chip:not([slot]):not([overflow-chip])'); + } + + #tabindex = RovingTabindexController.of(this, { + getItems: () => [ + ...Array.prototype.slice.call(this.#chips, 0, this.open ? this.#chips.length + : Math.min(this.#chips.length, this.numChips)), + this._overflowChip, + this._button, + ].filter(x => !!x), + }); + + /** + * active chip that receives focus when group receives focus + */ + get activeChip() { + const button = this.#tabindex.items.at(this.#tabindex.atFocusedItemIndex); + const shadow = button?.getRootNode() as ShadowRoot; + return shadow?.host as PfV5Chip; + } + + set activeChip(chip: HTMLElement) { + const button = chip.shadowRoot?.querySelector('button'); + if (button) { + this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(button); + } + } + + /** + * whether or not group has a category + */ + get hasCategory(): boolean { + return (this._categorySlotted || []).length > 0; + } + + get remaining(): number { + return this.#chips.length - this.numChips; + } + + constructor() { + super(); + this.addEventListener('remove', this.#onRemove); + } + + render(): TemplateResult<1> { + const empty = this.#chips.length <= 0; + return html` + + `; + } + + /** + * updates chips when they change + */ + @observes('accessibleCloseLabel') + @observes('numChips') + @observes('closeable') + @observes('open') + private chipsChanged(): void { + this.#updateOverflow(); + } + + /** + * handles chip group close + */ + #onCloseClick() { + this.dispatchEvent(new PfV5ChipGroupRemoveEvent()); + } + + /** + * handles overflow chip's click event + * @param event click event + */ + async #onMoreClick(event: Event) { + event.stopPropagation(); + this.open = !this.open; + await this.updateComplete; + this.chipsChanged(); + if (this._overflowChip) { + this.focusOnChip(this._overflowChip); + } + this.dispatchEvent(new PfV5ChipGroupExpandEvent()); + } + + #onSlotchange() { + this.requestUpdate(); + } + + #onRemove(event: Event) { + if (event instanceof PfV5ChipRemoveEvent) { + const index = this.#tabindex.atFocusedItemIndex; + if (event.chip) { + this.#tabindex.atFocusedItemIndex = index + 1; + } + } else if (event instanceof PfV5ChipGroupRemoveEvent) { + this.remove(); + } + } + + /** + * updates which chips are hidden + */ + #updateOverflow() { + this.#chips.forEach((chip, i) => { + chip.accessibleCloseLabel = this.accessibleCloseLabel; + const overflowHidden = i >= this.numChips && !this.open; + chip.hidden = overflowHidden; + }); + } + + /** + * Activates the specified chip and sets focus on it + * @param chip pf-v5-chip element + */ + focusOnChip(chip: PfV5Chip): void { + this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(chip); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-chip-group': PfV5ChipGroup; + } +} diff --git a/elements/pf-v5-chip/pf-v5-chip-shared.css b/elements/pf-v5-chip/pf-v5-chip-shared.css new file mode 100644 index 0000000000..338d53453c --- /dev/null +++ b/elements/pf-v5-chip/pf-v5-chip-shared.css @@ -0,0 +1,11 @@ +:host { + --pf-v5-c-chip__c-button--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-chip__c-button--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-chip__c-button--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-chip__c-button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-chip__c-button--MarginTop: calc(var(--pf-v5-c-chip--PaddingTop) * -1); + --pf-v5-c-chip__c-button--MarginRight: calc(var(--pf-v5-c-chip--PaddingRight) / 2 * -1); + --pf-v5-c-chip__c-button--MarginBottom: calc(var(--pf-v5-c-chip--PaddingBottom) * -1); + --pf-v5-c-chip__c-button--FontSize: var(--pf-global--FontSize--xs, 0.75rem); + --pf-v5-c-chip__c-badge--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); +} diff --git a/elements/pf-v5-chip/pf-v5-chip.css b/elements/pf-v5-chip/pf-v5-chip.css new file mode 100644 index 0000000000..375a1eac2f --- /dev/null +++ b/elements/pf-v5-chip/pf-v5-chip.css @@ -0,0 +1,129 @@ +:host { + /** Top padding for chip */ + --pf-v5-c-chip--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); + /** Right padding for chip */ + --pf-v5-c-chip--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + /** Bottom padding for chip */ + --pf-v5-c-chip--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + /** Left padding for chip */ + --pf-v5-c-chip--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + /** Background color for chip */ + --pf-v5-c-chip--BackgroundColor: var(--pf-global--Color--light-100, #fff); + /** Border radius for chip */ + --pf-v5-c-chip--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + /** Border color for chip */ + --pf-v5-c-chip--before--BorderColor: var(--pf-global--BorderColor--300, #f0f0f0); + /** Border width for chip */ + --pf-v5-c-chip--before--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); + /** Border radius for chip border (references chip border radius) */ + --pf-v5-c-chip--before--BorderRadius: var(--pf-v5-c-chip--BorderRadius); + /** Text color for overflow chip */ + --pf-v5-c-chip--m-overflow__text--Color: var(--pf-global--primary-color--100, #06c); + /** Background color for draggable chip */ + --pf-v5-c-chip--m-draggable--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); + /** Box shadow for draggable chip */ + --pf-v5-c-chip--m-draggable--BoxShadow: var(--pf-global--BoxShadow--sm, 0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06)); + /** Font size for draggable chip icon */ + --pf-v5-c-chip--m-draggable__icon--FontSize: var(--pf-global--icon--FontSize--sm, 0.625rem); + /** Font size for chip text */ + --pf-v5-c-chip__text--FontSize: var(--pf-global--FontSize--xs, 0.75rem); + /** Color for chip text */ + --pf-v5-c-chip__text--Color: var(--pf-global--Color--100, #151515); + /** Maximum width for chip text */ + --pf-v5-c-chip__text--MaxWidth: 16ch; + /** Left margin for chip icon */ + --pf-v5-c-chip__icon--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); + color: var(--pf-global--Color--100, #151515); + position: relative; + display: inline-flex; + align-items: center; + min-width: 0; + list-style: none; + /** Background color of chip */ + background-color: var(--pf-v5-c-chip--BackgroundColor); + /** Border radius of chip */ + border-radius: var(--pf-v5-c-chip--BorderRadius); + padding: + var(--pf-v5-c-chip--PaddingTop) + var(--pf-v5-c-chip--PaddingRight) + var(--pf-v5-c-chip--PaddingBottom) + var(--pf-v5-c-chip--PaddingLeft); +} + +[hidden], +:host([hidden]) { + display: none !important; +} + +div#outer { + display: contents; +} + +#outer:before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + border: + /** Border width */ + var(--pf-v5-c-chip--before--BorderWidth) + solid + /** Border color */ + var(--pf-v5-c-chip--before--BorderColor); + /** Border radius of chip border */ + border-radius: var(--pf-v5-c-chip--before--BorderRadius); +} + +span { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + position: relative; + /** Maximum width of chip text */ + max-width: var(--pf-v5-c-chip__text--MaxWidth); + /** Font size of chip text */ + font-size: var(--pf-v5-c-chip__text--FontSize); + /** Color of chip text */ + color: var(--pf-v5-c-chip__text--Color); +} + +:host([readonly]) span { + color: var(--pf-global--Color--200, #6a6e73); +} + +/* OVERFLOW */ + +button#outer { + display: flex; + background: none; + border: 1px solid transparent; +} + +button#outer span { + /** Color for overflow chip text */ + color: var(--pf-v5-c-chip--m-overflow__text--Color); +} + +/* CLOSE */ + +#close-button { + --pf-v5-icon--size: 12px; + --pf-v5-c-button--PaddingTop: var(--pf-v5-c-chip__c-button--PaddingTop); + --pf-v5-c-button--PaddingRight: var(--pf-v5-c-chip__c-button--PaddingRight); + --pf-v5-c-button--PaddingBottom: var(--pf-v5-c-chip__c-button--PaddingBottom); + --pf-v5-c-button--PaddingLeft: var(--pf-v5-c-chip__c-button--PaddingLeft); + --pf-v5-c-button--FontSize: var(--pf-v5-c-chip__c-button--FontSize); + margin-top: var(--pf-v5-c-chip__c-button--MarginTop); + margin-right: var(--pf-v5-c-chip__c-button--MarginRight); + margin-bottom: var(--pf-v5-c-chip__c-button--MarginBottom); + inset-block-start: 0.125em; +} + +::slotted(pf-v5-badge) { + font-size: var(--pf-global--FontSize--xs, 12px); + margin: 0 0 0 var(--pf-global--spacer--xs, 0.25rem) !important; + min-width: unset; +} diff --git a/elements/pf-v5-chip/pf-v5-chip.ts b/elements/pf-v5-chip/pf-v5-chip.ts new file mode 100644 index 0000000000..d7878a247b --- /dev/null +++ b/elements/pf-v5-chip/pf-v5-chip.ts @@ -0,0 +1,84 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; + +import styles from './pf-v5-chip.css'; +import shared from './pf-v5-chip-shared.css'; + +export class PfV5ChipRemoveEvent extends Event { + constructor(public chip: PfV5Chip) { + super('remove', { bubbles: true }); + } +} + +/** + * A **chip** is used to communicate a value or a set of attribute-value pairs within workflows that involve filtering a set of objects. + * @alias Chip + * @fires {ChipRemoveEvent} remove - Fires when chip is removed + * @fires {Event} click - when close button is clicked + */ +@customElement('pf-v5-chip') +export class PfV5Chip extends LitElement { + static readonly styles: CSSStyleSheet[] = [shared, styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** + * Accessible label for close button + */ + @property({ attribute: 'accessible-close-label', type: String }) accessibleCloseLabel = 'Close'; + + /** + * Flag indicating if chip is read-only and cannot be removed + */ + @property({ reflect: true, type: Boolean }) readonly = false; + + /** + * Flag indicating if chip is read-only and cannot be removed + */ + @property({ attribute: 'overflow-chip', reflect: true, type: Boolean }) overflowChip = false; + + render(): TemplateResult<1> { + return this.overflowChip ? html` + + ` : html` +
+ + + + + + +
+ `; + } + + #onClick() { + if (this.dispatchEvent(new PfV5ChipRemoveEvent(this))) { + this.remove(); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-chip': PfV5Chip; + } +} diff --git a/elements/pf-v5-chip/test/pf-chip-group.spec.ts b/elements/pf-v5-chip/test/pf-chip-group.spec.ts new file mode 100644 index 0000000000..2952ac858e --- /dev/null +++ b/elements/pf-v5-chip/test/pf-chip-group.spec.ts @@ -0,0 +1,232 @@ +import { expect, html, nextFrame } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { a11ySnapshot, querySnapshot, querySnapshotAll, type A11yTreeSnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { PfV5ChipGroup } from '../pf-v5-chip-group.js'; +import { PfV5Chip } from '../pf-v5-chip.js'; +import { sendKeys } from '@web/test-runner-commands'; + +function press(key: string) { + return async function() { + await sendKeys({ press: key }); + }; +} + +describe('', async function() { + let element: PfV5ChipGroup; + + describe('simply instantiating', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-chip-group')).to.be.an.instanceof(PfV5ChipGroup); + }); + + it('should upgrade', async function() { + element = await createFixture(html``); + const klass = customElements.get('pf-v5-chip-group'); + expect(element) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV5ChipGroup); + }); + }); + + describe('with 4 chips', function() { + let element: PfV5ChipGroup; + const updateComplete = () => element.updateComplete; + + beforeEach(async function() { + element = await createFixture(html` + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + `); + }); + + it('displays 3 chips and an overflow button', async function() { + const snapshot = await a11ySnapshot(); + expect(querySnapshotAll(snapshot, { name: /^Chip/ })).to.have.length(3); + expect(querySnapshotAll(snapshot, { role: 'button' })).to.have.length(4); + }); + + describe('Tab', function() { + beforeEach(press('Tab')); + beforeEach(nextFrame); + it('focuses the first close button', async function() { + const snapshot = await a11ySnapshot(); + const focused = querySnapshot(snapshot, { focused: true }); + expect(focused).to.have.property('name', 'Close'); + expect(focused).to.have.property('description', 'Chip 1'); + }); + describe('ArrowLeft', function() { + beforeEach(press('ArrowLeft')); + it('focuses the show less button', async function() { + const snapshot = await a11ySnapshot(); + const focused = querySnapshot(snapshot, { focused: true }); + expect(focused).to.have.property('name', '1 more'); + }); + describe('Enter', function() { + beforeEach(press('Enter')); + beforeEach(updateComplete); + it('should show all chips', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4); + }); + it('should show collapse button', async function() { + const snapshot = await a11ySnapshot(); + const buttons = querySnapshotAll(snapshot, { role: 'button' }); + expect(buttons).to.have.length(5); + }); + it('should focus collapse button', async function() { + const snapshot = await a11ySnapshot(); + expect(querySnapshot(snapshot, { focused: true })) + .to.have.property('name', 'show less'); + }); + }); + }); + }); + }); + + describe('with 4 chips and `closeable` attribute', function() { + let element: PfV5ChipGroup; + const updateComplete = () => element.updateComplete; + beforeEach(async function() { + element = await createFixture(html` + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + `); + }); + + beforeEach(updateComplete); + it('should have close button', async function() { + const snapshot = await a11ySnapshot(); + const last = snapshot.children?.at(-1); + expect(last?.name).to.equal('Close'); + expect(last?.role).to.equal('button'); + expect(last?.description).to.not.be.ok; + }); + + describe('clicking close button', function() { + beforeEach(() => element.focus()); + beforeEach(press('ArrowLeft')); + beforeEach(press('Enter')); + beforeEach(updateComplete); + it('should remove element', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children).to.not.be.ok; + }); + }); + }); + + describe('with 4 chips and `collapsed-text`, `expanded-text`, and `label` attributes', async function() { + let chip1: PfV5Chip; + let chip2: PfV5Chip; + let element: PfV5ChipGroup; + let snapshot: A11yTreeSnapshot; + const updateComplete = () => element.updateComplete; + + beforeEach(async function() { + element = await createFixture(html` + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + `); + [chip1, chip2] = document.querySelectorAll('pf-v5-chip'); + snapshot = await a11ySnapshot(); + }); + + it('has accessible label', function() { + const [offscreen] = snapshot.children!; + expect(offscreen?.name).to.equal('My Chip Group'); + }); + + it('is accessible', async function() { + await expect(element).to.be.accessible(); + }); + + describe('pressing `Tab`', function() { + beforeEach(press('Tab')); + beforeEach(updateComplete); + it('should focus', function() { + expect(document.activeElement).to.equal(chip1); + }); + }); + + describe('pressing `ArrowRight`', function() { + beforeEach(() => chip1.focus()); + beforeEach(press('ArrowRight')); + beforeEach(updateComplete); + it('should move to chip2', function() { + expect(document.activeElement).to.equal(chip2); + }); + }); + }); + + describe('with 4 chips and `num-chips="2"` attribute', function() { + beforeEach(async function() { + await createFixture(html` + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + `); + }); + it('only 2 chips should be visible', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(2); + }); + }); + + describe('with 4 chips and `num-chips="4"` attribute', function() { + let element: PfV5ChipGroup; + const updateComplete = () => element.updateComplete; + + beforeEach(async function() { + element = await createFixture(html` + + Chip 1 + Chip 2 + Chip 3 + Chip 4 + + `); + }); + + it('all 4 chips should be visible', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.filter(x => x.name.startsWith('Chip'))?.length).to.equal(4); + }); + + describe('keyboard navigating with arrow keys to third chip and pressing enter', function() { + beforeEach(press('Tab')); + beforeEach(press('ArrowRight')); + beforeEach(press('ArrowRight')); + beforeEach(press('Enter')); + beforeEach(updateComplete); + + it('should remove third chip', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.find(x => x.name === 'Chip 3')).to.not.be.ok; + }); + + it('should focus on close button', async function() { + const snapshot = await a11ySnapshot(); + expect(querySnapshot(snapshot, { focused: true })) + .to.be.ok + .and.have.property('name', 'Close'); + }); + }); + }); +}); diff --git a/elements/pf-chip/test/pf-chip.e2e.ts b/elements/pf-v5-chip/test/pf-chip.e2e.ts similarity index 95% rename from elements/pf-chip/test/pf-chip.e2e.ts rename to elements/pf-v5-chip/test/pf-chip.e2e.ts index 77e5e203e5..6a3f21c845 100644 --- a/elements/pf-chip/test/pf-chip.e2e.ts +++ b/elements/pf-v5-chip/test/pf-chip.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-chip'; +const tagName = 'pf-v5-chip'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-chip/test/pf-chip.spec.ts b/elements/pf-v5-chip/test/pf-chip.spec.ts similarity index 78% rename from elements/pf-chip/test/pf-chip.spec.ts rename to elements/pf-v5-chip/test/pf-chip.spec.ts index 7d39ae6dab..ca2a293cc2 100644 --- a/elements/pf-chip/test/pf-chip.spec.ts +++ b/elements/pf-v5-chip/test/pf-chip.spec.ts @@ -1,6 +1,6 @@ import { expect, html } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfChip } from '../pf-chip.js'; +import { PfV5Chip } from '../pf-v5-chip.js'; import { sendKeys } from '@web/test-runner-commands'; import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; @@ -17,25 +17,25 @@ function activeElement(element: HTMLElement) { return element.shadowRoot?.activeElement; } -describe('', async function() { +describe('', async function() { it('imperatively instantiates', function() { - expect(document.createElement('pf-chip')).to.be.an.instanceof(PfChip); + expect(document.createElement('pf-v5-chip')).to.be.an.instanceof(PfV5Chip); }); it('should upgrade', async function() { - const klass = customElements.get('pf-chip'); - expect(await createFixture(html``)) + const klass = customElements.get('pf-v5-chip'); + expect(await createFixture(html``)) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfChip); + .to.be.an.instanceOf(PfV5Chip); }); describe('simply instantiating', function() { - let element: PfChip; + let element: PfV5Chip; beforeEach(async function() { - element = await createFixture(html` - + element = await createFixture(html` + `); }); @@ -64,18 +64,18 @@ describe('', async function() { beforeEach(() => element.focus()); beforeEach(press('Enter')); it('should close', function() { - expect(document.querySelector('pf-chip')).to.be.null; + expect(document.querySelector('pf-v5-chip')).to.be.null; }); }); }); }); describe('with `overflow-chip` attribute', function() { - let element: PfChip; + let element: PfV5Chip; beforeEach(async function() { - element = await createFixture(html` - Overflow + element = await createFixture(html` + Overflow `); }); @@ -106,24 +106,24 @@ describe('', async function() { beforeEach(() => element.focus()); beforeEach(press('Enter')); it('should NOT close', function() { - expect(document.querySelector('pf-chip')).to.not.be.null; + expect(document.querySelector('pf-v5-chip')).to.not.be.null; }); }); describe('clicking element', function() { beforeEach(() => clickElementAtCenter(element)); it('should NOT close', function() { - expect(document.querySelector('pf-chip')).to.not.be.null; + expect(document.querySelector('pf-v5-chip')).to.not.be.null; }); }); }); describe('readonly', async function() { - let element: PfChip; + let element: PfV5Chip; beforeEach(async function() { - element = await createFixture(html` - + element = await createFixture(html` + `); }); diff --git a/elements/pf-v5-clipboard-copy/README.md b/elements/pf-v5-clipboard-copy/README.md new file mode 100644 index 0000000000..bce08e1814 --- /dev/null +++ b/elements/pf-v5-clipboard-copy/README.md @@ -0,0 +1,8 @@ +# Clipboard Copy +Copy-to-clipboard widget + +## Usage + +```html +Content to copy +``` diff --git a/elements/pf-v5-clipboard-copy/demo/code.html b/elements/pf-v5-clipboard-copy/demo/code.html new file mode 100644 index 0000000000..fe7a95dc66 --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/code.html @@ -0,0 +1,27 @@ +
+

JSON object (pre-formatted code)

+ + { "menu": { + "id": "file", + "value": "File", + "popup": { + "menuitem": [ + {"value": "New", "onclick": "CreateNewDoc()"}, + {"value": "Open", "onclick": "OpenDoc()"}, + {"value": "Close", "onclick": "CloseDoc()"} + ] + } + }} + +
+ + + + diff --git a/elements/pf-v5-clipboard-copy/demo/expandable.html b/elements/pf-v5-clipboard-copy/demo/expandable.html new file mode 100644 index 0000000000..b09518327d --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/expandable.html @@ -0,0 +1,17 @@ +
+ + Got a lot of text here, need to see all of it? + Click that arrow on the left side and check out the resulting expansion. + +
+ + + + diff --git a/elements/pf-v5-clipboard-copy/demo/index.html b/elements/pf-v5-clipboard-copy/demo/index.html new file mode 100644 index 0000000000..4f2c62fc7a --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/index.html @@ -0,0 +1,14 @@ +
+ +
+ + + + diff --git a/elements/pf-v5-clipboard-copy/demo/inline-compact-code.html b/elements/pf-v5-clipboard-copy/demo/inline-compact-code.html new file mode 100644 index 0000000000..0bde4ce5fb --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/inline-compact-code.html @@ -0,0 +1,14 @@ +
+ 2.3.4-2-redhat +
+ + + + diff --git a/elements/pf-v5-clipboard-copy/demo/inline-compact.html b/elements/pf-v5-clipboard-copy/demo/inline-compact.html new file mode 100644 index 0000000000..8046701a73 --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/inline-compact.html @@ -0,0 +1,32 @@ +
+ 2.3.4-2-redhat +

Inline compact with additional action

+ 2.3.4-2-redhat + + + + +
+

Inline compact in sentence

+

Basic

+
+

Lorem ipsum 2.3.4-2-redhat

+ +

Long copy string

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, ullamcorper pharetra lacus nibh vitae enim.

+ +

Long copy string in block

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. + https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, ullamcorper pharetra lacus nibh vitae enim.

+
+ + + + diff --git a/elements/pf-v5-clipboard-copy/demo/read-only-expandable.html b/elements/pf-v5-clipboard-copy/demo/read-only-expandable.html new file mode 100644 index 0000000000..bce867384a --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/read-only-expandable.html @@ -0,0 +1,17 @@ +
+ + Got a lot of text here, need to see all of it? + Click that arrow on the left side and check out the resulting expansion. + +
+ + + + diff --git a/elements/pf-v5-clipboard-copy/demo/read-only-expanded.html b/elements/pf-v5-clipboard-copy/demo/read-only-expanded.html new file mode 100644 index 0000000000..114c584fd8 --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/read-only-expanded.html @@ -0,0 +1,17 @@ +
+ +

Got a lot of text here, need to see all of it? Click that arrow on the left side and check out the resulting expansion.

+

asodifna osdif

+
+
+ + + + diff --git a/elements/pf-v5-clipboard-copy/demo/read-only.html b/elements/pf-v5-clipboard-copy/demo/read-only.html new file mode 100644 index 0000000000..519b74d78c --- /dev/null +++ b/elements/pf-v5-clipboard-copy/demo/read-only.html @@ -0,0 +1,14 @@ +
+ +
+ + + + diff --git a/elements/pf-clipboard-copy/docs/CHANGELOG.old.md b/elements/pf-v5-clipboard-copy/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-clipboard-copy/docs/CHANGELOG.old.md rename to elements/pf-v5-clipboard-copy/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-clipboard-copy/docs/pf-v5-clipboard-copy.md b/elements/pf-v5-clipboard-copy/docs/pf-v5-clipboard-copy.md new file mode 100644 index 0000000000..a667c4a728 --- /dev/null +++ b/elements/pf-v5-clipboard-copy/docs/pf-v5-clipboard-copy.md @@ -0,0 +1,106 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + +{% endrenderOverview %} + +{% band header="Usage" %} + ### Read-only + {% htmlexample %} + + {% endhtmlexample %} + + ### Expanded + {% htmlexample %} + + Got a lot of text here, need to see all of it? + Click that arrow on the left side and check out the resulting expansion. + + {% endhtmlexample %} + + ### Read-only expanded + {% htmlexample %} + + Got a lot of text here, need to see all of it? + Click that arrow on the left side and check out the resulting expansion. + + {% endhtmlexample %} + + ### Read-only expanded by default + {% htmlexample %} + +

Got a lot of text here, need to see all of it? Click that arrow on the left side and check out the resulting expansion.

+

asodifna osdif

+
+ {% endhtmlexample %} + + ### JSON object (pre-formatted code) + {% htmlexample %} + + { "menu": { + "id": "file", + "value": "File", + "popup": { + "menuitem": [ + {"value": "New", "onclick": "CreateNewDoc()"}, + {"value": "Open", "onclick": "OpenDoc()"}, + {"value": "Close", "onclick": "CloseDoc()"} + ] + } + }} + + {% endhtmlexample %} + + ### Inline compact + {% htmlexample %} + 2.3.4-2-redhat + {% endhtmlexample %} + + ### Inline compact code + {% htmlexample %} + 2.3.4-2-redhat + {% endhtmlexample %} + + ### Inline compact with additional action + {% htmlexample %} + 2.3.4-2-redhat + + + + + {% endhtmlexample %} + + ### Inline compact in sentence + #### Basic + {% htmlexample %} +

Lorem ipsum 2.3.4-2-redhat

+ {% endhtmlexample %} + + #### Long copy string + {% htmlexample %} +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 + Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, + ullamcorper pharetra lacus nibh vitae enim.

+ {% endhtmlexample %} + + #### Long copy string in block + {% htmlexample %} +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. + https://app.openshift.io/path/sub-path/sub-sub-path/?runtime=quarkus/12345678901234567890/abcdefghijklmnopqrstuvwxyz1234567890 + Mauris luctus, libero nec dapibus ultricies, urna purus pretium mauris, + ullamcorper pharetra lacus nibh vitae enim. +

+ {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-clipboard-copy/docs/screenshot.png b/elements/pf-v5-clipboard-copy/docs/screenshot.png similarity index 100% rename from elements/pf-clipboard-copy/docs/screenshot.png rename to elements/pf-v5-clipboard-copy/docs/screenshot.png diff --git a/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.css b/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.css new file mode 100644 index 0000000000..4652ced7a1 --- /dev/null +++ b/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.css @@ -0,0 +1,187 @@ +:host { + /** Transition for the toggle icon */ + --pf-v5-c-clipboard-copy__toggle-icon--Transition: .2s ease-in 0s; + /** Rotate value for the toggle icon when expanded */ + --pf-v5-c-clipboard-copy--m-expanded__toggle-icon--Rotate: 90deg; + /** PaddingTop for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--PaddingTop: var(--pf-global--spacer--md, 1rem); + /** PaddingRight for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--PaddingRight: var(--pf-global--spacer--md, 1rem); + /** PaddingBottom for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--PaddingBottom: var(--pf-global--spacer--md, 1rem); + /** PaddingLeft for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--PaddingLeft: var(--pf-global--spacer--md, 1rem); + /** BackgroundColor for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #fff); + /** BorderTopWidth for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--BorderTopWidth: 0; + /** BorderRightWidth for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--BorderRightWidth: var(--pf-global--BorderWidth--sm, 1px); + /** BorderBottomWidth for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--BorderBottomWidth: var(--pf-global--BorderWidth--sm, 1px); + /** BorderLeftWidth for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--BorderLeftWidth: var(--pf-global--BorderWidth--sm, 1px); + /** BorderColor for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); + /** OutlineOffset for the expandable content */ + --pf-v5-c-clipboard-copy__expandable-content--OutlineOffset: calc(-1 * var(--pf-global--spacer--xs, 0.25rem)); + /** PaddingTop for the inline variant */ + --pf-v5-c-clipboard-copy--m-inline--PaddingTop: 0; + /** PaddingBottom for the inline variant */ + --pf-v5-c-clipboard-copy--m-inline--PaddingBottom: 0; + /** PaddingLeft for the inline variant */ + --pf-v5-c-clipboard-copy--m-inline--PaddingLeft: var(--pf-global--spacer--xs, 0.25rem); + /** BackgroundColor for the inline variant */ + --pf-v5-c-clipboard-copy--m-inline--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); + /** FontFamily for the code text */ + --pf-v5-c-clipboard-copy__text--m-code--FontFamily: var(--pf-global--FontFamily--monospace, "Liberation Mono", consolas, "SFMono-Regular", menlo, monaco, "Courier New", monospace); + /** FontSize for the code text */ + --pf-v5-c-clipboard-copy__text--m-code--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** MarginTop for actions item */ + --pf-v5-c-clipboard-copy__actions-item--MarginTop: calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)); + /** MarginBottom for actions item */ + --pf-v5-c-clipboard-copy__actions-item--MarginBottom: calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)); + /** PaddingTop for actions item button */ + --pf-v5-c-clipboard-copy__actions-item--button--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); + /** PaddingRight for actions item button */ + --pf-v5-c-clipboard-copy__actions-item--button--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + /** PaddingBottom for actions item button */ + --pf-v5-c-clipboard-copy__actions-item--button--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + /** PaddingLeft for actions item button */ + --pf-v5-c-clipboard-copy__actions-item--button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-icon--size: var(--pf-global--FontSize--md, 1rem); +} + +[hidden], +[inert], +[inert]::slotted(*) { + display: none !important; +} + +#container { + flex-direction: column; +} + +#container, +#input-group, +#wrapper { + display: flex; +} + +.inline #wrapper { + display: inline-flex; +} + +#input-group > * + * { + margin-left: -1px; +} + +input { + color: var(--pf-v5-c-form-control--Color); + width: var(--pf-v5-c-form-control--Width); + padding: + var(--pf-v5-c-form-control--PaddingTop) + var(--pf-v5-c-form-control--PaddingRight) + var(--pf-v5-c-form-control--PaddingBottom) + var(--pf-v5-c-form-control--PaddingLeft); + font-size: var(--pf-v5-c-form-control--FontSize); + line-height: var(--pf-v5-c-form-control--LineHeight); + background-color: var(--pf-v5-c-form-control--BackgroundColor); + background-repeat: no-repeat; + border: var(--pf-v5-c-form-control--BorderWidth) solid; + border-color: + var(--pf-v5-c-form-control--BorderTopColor) + var(--pf-v5-c-form-control--BorderRightColor) + var(--pf-v5-c-form-control--BorderBottomColor) + var(--pf-v5-c-form-control--BorderLeftColor); + border-radius: var(--pf-v5-c-form-control--BorderRadius); + margin: 0; + appearance: none; + height: var(--pf-v5-c-form-control--Height); + text-overflow: ellipsis; + flex: 1 1 auto; +} + +input[disabled] { + background-color: var(--pf-v5-c-form-control--readonly--BackgroundColor); +} + +textarea { + display: flex; + flex: 1 1 auto; + padding: + var(--pf-v5-c-clipboard-copy__expandable-content--PaddingTop) + var(--pf-v5-c-clipboard-copy__expandable-content--PaddingRight) + var(--pf-v5-c-clipboard-copy__expandable-content--PaddingBottom) + var(--pf-v5-c-clipboard-copy__expandable-content--PaddingLeft); + word-wrap: break-word; + background-color: var(--pf-v5-c-clipboard-copy__expandable-content--BackgroundColor); + background-clip: padding-box; + border: solid var(--pf-v5-c-clipboard-copy__expandable-content--BorderColor); + border-width: + var(--pf-v5-c-clipboard-copy__expandable-content--BorderTopWidth) + var(--pf-v5-c-clipboard-copy__expandable-content--BorderRightWidth) + var(--pf-v5-c-clipboard-copy__expandable-content--BorderBottomWidth) + var(--pf-v5-c-clipboard-copy__expandable-content--BorderLeftWidth); + box-shadow: var(--pf-v5-c-clipboard-copy__expandable-content--BoxShadow); + margin: 0; + color: inherit; + font-family: inherit; +} + +#input-group { + display: flex; + height: 100%; +} + +#container.code textarea { + font-family: var(--pf-global--FontFamily--monospace, + var(--pf-global--FontFamily--redhat--monospace, + "RedHatMono", + "Liberation Mono", + consolas, + "SFMono-Regular", + menlo, + monaco, + "Courier New", + monospace)); +} + +#container.expanded #expand-button pf-v5-icon { + rotate: 90deg; +} + +#container.inline { + display: inline; + background-color: var(--pf-v5-c-clipboard-copy--m-inline--BackgroundColor); + padding-block-start: var(--pf-v5-c-clipboard-copy--m-inline--PaddingTop); + padding-block-end: var(--pf-v5-c-clipboard-copy--m-inline--PaddingBottom); + padding-inline-start: var(--pf-v5-c-clipboard-copy--m-inline--PaddingLeft); + word-break: break-word; + white-space: normal; +} + +#container:is(.compact, .inline) #input-group { + display: contents; +} + +#container:is(.compact, .inline) #input-group { + background-color: var(--pf-v5-c-button--m-plain--BackgroundColor, + var(--pf-global--BackgroundColor--200, #f0f0f0)); +} + +#container.compact.block { + display: block; + background-color: var(--pf-v5-c-clipboard-copy--m-inline--BackgroundColor); +} + +#container:is(.compact, .inline) #copy-button, +#container:is(.compact, .inline) slot[name="actions"]::slotted(*) { + --pf-v5-c-button--PaddingTop: var(--pf-v5-c-clipboard-copy__actions-item--button--PaddingTop) !important; + --pf-v5-c-button--PaddingRight: var(--pf-v5-c-clipboard-copy__actions-item--button--PaddingRight) !important; + --pf-v5-c-button--PaddingBottom: var(--pf-v5-c-clipboard-copy__actions-item--button--PaddingBottom) !important; + --pf-v5-c-button--PaddingLeft: var(--pf-v5-c-clipboard-copy__actions-item--button--PaddingLeft) !important; + margin-block-start: calc(-1 * var(--pf-v5-c-button--PaddingTop)); + margin-block-end: calc(-1 * var(--pf-v5-c-button--PaddingBottom)); +} + diff --git a/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.ts b/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.ts new file mode 100644 index 0000000000..ae30df1548 --- /dev/null +++ b/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.ts @@ -0,0 +1,179 @@ +import { LitElement, html, isServer, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; + +import styles from './pf-v5-clipboard-copy.css'; +import formControlStyles from '../form-control.css'; + +import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; +import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; +import '@patternfly/elements/pf-v5-tooltip/pf-v5-tooltip.js'; + +const sleep = (ms?: number) => new Promise(r => setTimeout(r, ms)); + +export class PfV5ClipboardCopyCopiedEvent extends Event { + constructor(public text: string) { + super('copy', { bubbles: true }); + } +} + +/** + * The **clipboard copy** component allows users to quickly and easily copy content to their clipboard. + * @alias Clipboard Copy + * @fires {PfV5ClipboardCopyCopiedEvent} copy - when the text snippet is successfully copied. + */ +@customElement('pf-v5-clipboard-copy') +export class PfV5ClipboardCopy extends LitElement { + static readonly styles: CSSStyleSheet[] = [formControlStyles, styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** Tooltip message to display when clicking the copy button */ + @property({ attribute: 'click-tip' }) clickTip = 'Successfully copied to clipboard!'; + + /** Tooltip message to display when hover the copy button */ + @property({ attribute: 'hover-tip' }) hoverTip = 'Copy to clipboard'; + + /** Accessible label to use on the text input. */ + @property({ attribute: 'accessible-text-label' }) accessibleTextLabel = 'Copyable input'; + + /** Accessible label to use on the toggle button. */ + @property({ attribute: 'accessible-toggle-label' }) accessibleToggleLabel = 'Show content'; + + /** Delay in ms before the tooltip appears. */ + @property({ type: Number, attribute: 'entry-delay' }) entryDelay = 300; + + /** Delay in ms before the tooltip disappears. */ + @property({ type: Number, attribute: 'exit-delay' }) exitDelay = 1500; + + /** Flag to determine if inline clipboard copy should be block styling */ + @property({ type: Boolean, reflect: true }) block = false; + + /** Flag to determine if clipboard copy content includes code */ + @property({ type: Boolean, reflect: true }) code = false; + + /** Flag to determine if clipboard copy is in the expanded state */ + @property({ type: Boolean, reflect: true }) expanded = false; + + /** Implies not `inline`. */ + @property({ type: Boolean, reflect: true }) expandable = false; + + /** Flag to show if the input is read only. */ + @property({ type: Boolean, reflect: true }) readonly = false; + + /** Implies not expandable. Overrules `expandable`. */ + @property({ type: Boolean, reflect: true }) inline = false; + + /** Flag to determine if inline clipboard copy should have compact styling */ + @property({ type: Boolean, reflect: true }) compact = false; + + /** String to copy */ + @property() value = ''; + + #copied = false; + + #mo = new MutationObserver(() => this.#onMutation()); + + connectedCallback(): void { + super.connectedCallback(); + this.#mo.observe(this, { characterData: true }); + if (!isServer) { + this.#onMutation(); + } + } + + /** + * @todo fix the collapsed whitespace between the end of the "inline-compact" variant and the text node. + * This demonstrates the collapsed whitespace issue. + * The extra space between the closing slot tag and the closing template literal results in a collapsed whitespace. + */ + render(): TemplateResult<1> { + const { expanded, expandable, inline, compact, code, block, readonly } = this; + return html` +
+
+
+ + + +
+ ${this.value} + + + + + ${this.#copied ? this.clickTip : this.hoverTip} + + + +
+ +
+ `; + } + + #onClick() { + this.expanded = !this.expanded; + } + + #onChange(e: Event) { + const { value } = e.target as HTMLInputElement || HTMLTextAreaElement; + this.value = value; + } + + #onMutation() { + if (this.childNodes?.length > 0) { + this.value = this.getAttribute('value') ?? this.#dedent(Array.from(this.childNodes, child => + (child instanceof Element || child instanceof Text) ? (child.textContent ?? '') : '') + .join('')); + } + } + + #dedent(str: string): string { + const stripped = str.replace(/^\n/, ''); + const match = stripped.match(/^\s+/); + return match ? stripped.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str; + } + + /** + * Copy the current value to the clipboard. + */ + async copy(): Promise { + await navigator.clipboard.writeText(this.value); + this.dispatchEvent(new PfV5ClipboardCopyCopiedEvent(this.value)); + await sleep(this.entryDelay); + this.#copied = true; + this.requestUpdate(); + await sleep(this.exitDelay); + this.#copied = false; + this.requestUpdate(); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-clipboard-copy': PfV5ClipboardCopy; + } +} diff --git a/elements/pf-clipboard-copy/test/pf-clipboard-copy.e2e.ts b/elements/pf-v5-clipboard-copy/test/pf-clipboard-copy.e2e.ts similarity index 94% rename from elements/pf-clipboard-copy/test/pf-clipboard-copy.e2e.ts rename to elements/pf-v5-clipboard-copy/test/pf-clipboard-copy.e2e.ts index 0d2f1f8da4..e10a81becd 100644 --- a/elements/pf-clipboard-copy/test/pf-clipboard-copy.e2e.ts +++ b/elements/pf-v5-clipboard-copy/test/pf-clipboard-copy.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-clipboard-copy'; +const tagName = 'pf-v5-clipboard-copy'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-clipboard-copy/test/pf-clipboard-copy.spec.ts b/elements/pf-v5-clipboard-copy/test/pf-clipboard-copy.spec.ts similarity index 86% rename from elements/pf-clipboard-copy/test/pf-clipboard-copy.spec.ts rename to elements/pf-v5-clipboard-copy/test/pf-clipboard-copy.spec.ts index 94bbf01a86..3ef8fd3ac0 100644 --- a/elements/pf-clipboard-copy/test/pf-clipboard-copy.spec.ts +++ b/elements/pf-v5-clipboard-copy/test/pf-clipboard-copy.spec.ts @@ -3,12 +3,12 @@ import type { SinonStub } from 'sinon'; import { sendKeys } from '@web/test-runner-commands'; import { expect, html } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfClipboardCopy } from '@patternfly/elements/pf-clipboard-copy/pf-clipboard-copy.js'; +import { PfV5ClipboardCopy } from '@patternfly/elements/pf-v5-clipboard-copy/pf-v5-clipboard-copy.js'; import { stub } from 'sinon'; -describe('', function() { - let element: PfClipboardCopy; +describe('', function() { + let element: PfV5ClipboardCopy; beforeEach(function stubCopy() { stub(navigator.clipboard, 'writeText'); }); @@ -18,21 +18,21 @@ describe('', function() { }); beforeEach(async function() { - element = await createFixture(html` - + element = await createFixture(html` + `); }); it('imperatively instantiates', function() { - expect(document.createElement('pf-clipboard-copy')).to.be.an.instanceof(PfClipboardCopy); + expect(document.createElement('pf-v5-clipboard-copy')).to.be.an.instanceof(PfV5ClipboardCopy); }); it('should upgrade', async function() { - const klass = customElements.get('pf-clipboard-copy'); + const klass = customElements.get('pf-v5-clipboard-copy'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfClipboardCopy); + .to.be.an.instanceOf(PfV5ClipboardCopy); }); describe('setting value attribute', function() { diff --git a/elements/pf-v5-code-block/README.md b/elements/pf-v5-code-block/README.md new file mode 100644 index 0000000000..602af5d35f --- /dev/null +++ b/elements/pf-v5-code-block/README.md @@ -0,0 +1,77 @@ +# PatternFly Elements Code Block +`` is a component that contains two or more lines of ready-only +code. The code in a code block can be copied to the clipboard. + +Read more about Code Block in the [PatternFly Elements Code Block +documentation](https://patternflyelements.org/components/code-block) + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/pf-v5-code-block +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/pf-v5-code-block'; +``` + +## Usage +### Basic + +Place your code in a `script` tag with a [non-javascript mimetype][mime]. +JavaScript snippets **must** use the `text/javascript-sample` script type. +Script text content will be automatically dedented. + + +To add copy-to-clipboard functionality, be sure to import +`@patternfly/pf-v5-clipboard` and add the `pf-v5-clipboard` to the `action` slot. + +```html + + + +``` + +### Expandable + +If a block of code is long and you'd like to hide some of the code to take up +less height on the page, place the code you'd like to hide in a `script` with +`data-expand` attribute present. + +It is important that you place the span right next to the first `script`, +because of how the whitespace will be handled. Failing to do this can result in +unwanted line breaks. + +```html + + + + +``` diff --git a/elements/pf-v5-code-block/demo/expandable.html b/elements/pf-v5-code-block/demo/expandable.html new file mode 100644 index 0000000000..45a052191d --- /dev/null +++ b/elements/pf-v5-code-block/demo/expandable.html @@ -0,0 +1,25 @@ +
+ + + + +
+ + + + diff --git a/elements/pf-v5-code-block/demo/expanded.html b/elements/pf-v5-code-block/demo/expanded.html new file mode 100644 index 0000000000..3290971ca8 --- /dev/null +++ b/elements/pf-v5-code-block/demo/expanded.html @@ -0,0 +1,25 @@ +
+ + + + +
+ + + + diff --git a/elements/pf-v5-code-block/demo/index.html b/elements/pf-v5-code-block/demo/index.html new file mode 100644 index 0000000000..0abc1bed26 --- /dev/null +++ b/elements/pf-v5-code-block/demo/index.html @@ -0,0 +1,25 @@ +
+ + + + +
+ + + + diff --git a/elements/pf-v5-code-block/demo/javascript-sample.html b/elements/pf-v5-code-block/demo/javascript-sample.html new file mode 100644 index 0000000000..97139cafbd --- /dev/null +++ b/elements/pf-v5-code-block/demo/javascript-sample.html @@ -0,0 +1,20 @@ +
+

JavaScript snippets must use the text/javascript-sample script type.

+ + + + +
+ + + + diff --git a/elements/pf-v5-code-block/demo/noscript.html b/elements/pf-v5-code-block/demo/noscript.html new file mode 100644 index 0000000000..896c5e42c5 --- /dev/null +++ b/elements/pf-v5-code-block/demo/noscript.html @@ -0,0 +1,51 @@ +
+

To display code snippets when javascript is disabled, use the following HTML snippet:

+ + + + + + +
+ + + + diff --git a/elements/pf-code-block/docs/CHANGELOG.old.md b/elements/pf-v5-code-block/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-code-block/docs/CHANGELOG.old.md rename to elements/pf-v5-code-block/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-code-block/docs/pf-v5-code-block.md b/elements/pf-v5-code-block/docs/pf-v5-code-block.md new file mode 100644 index 0000000000..f46ab527a1 --- /dev/null +++ b/elements/pf-v5-code-block/docs/pf-v5-code-block.md @@ -0,0 +1,80 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + Code Block is a component that contains two or more lines of read-only code. The code in a code block can be copied to the clipboard. + + + + + +{% endrenderOverview %} + +{% band header="Usage" %} +### Basic + +Place your code in a `script` tag with a [non-javascript mimetype][mime]. +JavaScript snippets **must** use the `text/javascript-sample` script type. +Script text content will be automatically dedented. + +To add copy-to-clipboard functionality, be sure to import `@patternfly/pf-v5-clipboard` and add the `pf-v5-clipboard` to the `action` slot. + +{% htmlexample %} + + + + +{% endhtmlexample %} + +### Expandable + +If a block of code is long and you'd like to hide some of the code to take us less height on the page, place the code you'd like to hide in a `span` with `slot="expandable-code"`. + +It is important that you place the span right next to the ` + +
+{% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} + +[mime]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#textjavascript diff --git a/elements/pf-code-block/docs/screenshot.png b/elements/pf-v5-code-block/docs/screenshot.png similarity index 100% rename from elements/pf-code-block/docs/screenshot.png rename to elements/pf-v5-code-block/docs/screenshot.png diff --git a/elements/pf-v5-code-block/pf-v5-code-block.css b/elements/pf-v5-code-block/pf-v5-code-block.css new file mode 100644 index 0000000000..593d93ff48 --- /dev/null +++ b/elements/pf-v5-code-block/pf-v5-code-block.css @@ -0,0 +1,66 @@ +:host { + display: block; + /** Background color for code block */ + background-color: var(--pf-v5-c-code-block--BackgroundColor, #f0f0f0); + /** Font size for code block pre element */ + font-size: var(--pf-v5-c-code-block__pre--FontSize, 0.875rem); + /** Font family for code block pre element */ + font-family: var(--pf-v5-c-code-block__pre--FontFamily, "Liberation Mono", consolas, "SFMono-Regular", menlo, monaco, "Courier New", monospace); +} + +[hidden] { + display: none !important; +} + +#container { + margin: 0; + /** Top padding for code block content */ + padding-top: var(--pf-v5-c-code-block__content--PaddingTop, 1rem); + /** Right padding for code block content */ + padding-right: var(--pf-v5-c-code-block__content--PaddingRight, 1rem); + /** Bottom padding for code block content */ + padding-bottom: var(--pf-v5-c-code-block__content--PaddingBottom, 1rem); + /** Left padding for code block content */ + padding-left: var(--pf-v5-c-code-block__content--PaddingLeft, 1rem); +} + +#header { + /** Border bottom width for code block header */ + border-bottom: var(--pf-v5-c-code-block__header--BorderBottomWidth, 1px) solid + /** Border bottom color for code block header */ + var(--pf-v5-c-code-block__header--BorderBottomColor, #d2d2d2); +} + +pre { + margin: 0; +} + +slot[name="actions"] { + display: flex; + justify-content: end; +} + +#expanded { + display: inline; +} + +button { + display: flex; + background: none; + border: none; + padding: 6px 16px 6px 0; + color: #06c; + cursor: pointer; + font-size: 16px; +} + +button svg { + color: #151515; + margin-right: 12px; + transition: .2s ease-in 0s; + vertical-align: -0.125em; +} + +.expanded button svg { + transform: rotate(-90deg); +} diff --git a/elements/pf-v5-code-block/pf-v5-code-block.ts b/elements/pf-v5-code-block/pf-v5-code-block.ts new file mode 100644 index 0000000000..8bbc86c1b7 --- /dev/null +++ b/elements/pf-v5-code-block/pf-v5-code-block.ts @@ -0,0 +1,76 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; +import styles from './pf-v5-code-block.css'; + +function dedent(str: string): string { + const stripped = str.replace(/^\n/, ''); + const match = stripped.match(/^\s+/); + return match ? stripped.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str; +} + +/** + * A **code block** is a component that contains 2 or more lines of read-only code. The code in a code block can be copied to the clipboard. + * @alias Code Block + */ +@customElement('pf-v5-code-block') +export class PfV5CodeBlock extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + /** Flag for whether the code block is expanded */ + @property({ type: Boolean, reflect: true }) expanded = false; + + get #expandedContent(): string { + return this.querySelector?.('script[data-expand]')?.textContent ?? ''; + } + + get #content() { + const script = this.querySelector?.('script[type]'); + if (script?.type !== 'text/javascript-sample' + && !!script?.type.match(/(j(ava)?|ecma|live)script/)) { + return ''; + } else { + return dedent(script?.textContent ?? ''); + } + } + + override render(): TemplateResult<1> { + const { expanded } = this; + return html` + +
+
${this.#content}${this.#expandedContent}
+ +
+ `; + } + + #toggle() { + this.expanded = !this.expanded; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-code-block': PfV5CodeBlock; + } +} diff --git a/elements/pf-code-block/test/pf-code-block.e2e.ts b/elements/pf-v5-code-block/test/pf-code-block.e2e.ts similarity index 95% rename from elements/pf-code-block/test/pf-code-block.e2e.ts rename to elements/pf-v5-code-block/test/pf-code-block.e2e.ts index 5df986cfe4..71f6e70dde 100644 --- a/elements/pf-code-block/test/pf-code-block.e2e.ts +++ b/elements/pf-v5-code-block/test/pf-code-block.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-code-block'; +const tagName = 'pf-v5-code-block'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-code-block/test/pf-code-block.spec.ts b/elements/pf-v5-code-block/test/pf-code-block.spec.ts new file mode 100644 index 0000000000..e36227c368 --- /dev/null +++ b/elements/pf-v5-code-block/test/pf-code-block.spec.ts @@ -0,0 +1,98 @@ +import { expect, html, nextFrame } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { PfV5CodeBlock } from '@patternfly/elements/pf-v5-code-block/pf-v5-code-block.js'; + +const element = html` + + + + +`; + +const expandElement = html` + + + + +`; + +const expandElementByDefault = html` + + + + +`; + +describe('', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-code-block')).to.be.an.instanceof(PfV5CodeBlock); + }); + + it('should upgrade', async function() { + const el = await createFixture (element); + const klass = customElements.get('pf-v5-code-block'); + expect(el) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV5CodeBlock); + }); + + it('should not show a "Show more" button by default', async function() { + const el = await createFixture (element); + const button = el.shadowRoot?.querySelector('button'); + expect(button?.hidden).to.be.true; + }); + + it('should show a "Show more" button if content is present in the expandable-code slot', async function() { + const expandEl = await createFixture(expandElement); + const expandButton = expandEl.shadowRoot?.querySelector('button'); + expect(expandButton).to.not.be.null; + }); + + it('should expand the code block when the "Show more" button is clicked', async function() { + const expandEl = await createFixture(expandElement); + const expandButton = expandEl.shadowRoot?.querySelector('button'); + expandButton?.click(); + await nextFrame(); + expect(expandEl.hasAttribute('expanded')).to.be.true; + expect(expandButton?.getAttribute('aria-expanded')).to.equal('true'); + }); + + it('should toggle the "Show more" button text to "Show less" when the button is clicked', async function() { + const expandEl = await createFixture(expandElement); + const expandButton = expandEl.shadowRoot?.querySelector('button'); + expandButton?.click(); + await nextFrame(); + expect(expandButton?.textContent?.trim()).to.equal('Show less'); + }); + + it('should be expanded by default if the expanded attribute is present on render', async function() { + const expandByDefaultEl = await createFixture(expandElementByDefault); + const expandButton = expandByDefaultEl.shadowRoot?.querySelector('button'); + expect(expandButton?.textContent?.trim()).to.equal('Show less'); + }); +}); diff --git a/elements/pf-v5-dropdown/README.md b/elements/pf-v5-dropdown/README.md new file mode 100644 index 0000000000..3237433b29 --- /dev/null +++ b/elements/pf-v5-dropdown/README.md @@ -0,0 +1,46 @@ +# Dropdown + +A dropdown presents a menu of actions or links in a constrained space that will +trigger a process or navigate to a new location. + +Read more about dropdown in the [PatternFly Elements Dropdown +documentation](https://patternflyelements.org/components/dropdown) + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-dropdown/pf-v5-dropdown.js'; +``` + +## Usage + +```html + + item4 +
+ + item1 + item2 +
+ item3 +
+ + item1 + item2 + +
+``` diff --git a/elements/pf-v5-dropdown/context.ts b/elements/pf-v5-dropdown/context.ts new file mode 100644 index 0000000000..db3f422e54 --- /dev/null +++ b/elements/pf-v5-dropdown/context.ts @@ -0,0 +1,9 @@ +import type { Context } from '@lit/context'; +import { createContextWithRoot } from '@patternfly/pfe-core/functions/context.js'; + +export interface PfV5DropdownContext { + disabled: boolean; +} + +export const context: Context = + createContextWithRoot(Symbol('pf-v5-dropdown-menu-context')); diff --git a/elements/pf-v5-dropdown/demo/custom-toggle.html b/elements/pf-v5-dropdown/demo/custom-toggle.html new file mode 100644 index 0000000000..6de7d42d54 --- /dev/null +++ b/elements/pf-v5-dropdown/demo/custom-toggle.html @@ -0,0 +1,131 @@ +

+ + Note: When using a custom toggle, you must slot in a pf-v5-dropdown-menu. + + + + + +

+ + + + Control toggle + + + + item4 +
+ + item1 + item2 +
+ item3 +
+ + item1 + item2 + item3 + disabled + +
+
+ + + Primary toggle + + item4 +
+ + item1 + item2 +
+ item3 +
+ + item1 + item2 + item3 + disabled + +
+
+ + + Secondary toggle + + item4 +
+ + item1 + item2 +
+ item3 +
+ + item1 + item2 + item3 + disabled + +
+
+ + + Plain toggle + + item4 +
+ + item1 + item2 +
+ item3 +
+ + item1 + item2 + item3 + disabled + +
+
+ + + + + item4 +
+ + item1 + item2 +
+ item3 +
+ + item1 + item2 + item3 + disabled + +
+
+ + + + + diff --git a/elements/pf-v5-dropdown/demo/descriptions.html b/elements/pf-v5-dropdown/demo/descriptions.html new file mode 100644 index 0000000000..88f01603ec --- /dev/null +++ b/elements/pf-v5-dropdown/demo/descriptions.html @@ -0,0 +1,11 @@ + + item1 + item2le deuxième élément + item3 + item4 + + + + diff --git a/elements/pf-v5-dropdown/demo/disabled-item.html b/elements/pf-v5-dropdown/demo/disabled-item.html new file mode 100644 index 0000000000..135a230970 --- /dev/null +++ b/elements/pf-v5-dropdown/demo/disabled-item.html @@ -0,0 +1,17 @@ + + Toggle dropdown + item4 +
+ + item1 + item2 +
+ item3 +
+ disabled item +
+ + + diff --git a/elements/pf-v5-dropdown/demo/disabled.html b/elements/pf-v5-dropdown/demo/disabled.html new file mode 100644 index 0000000000..bb64906e25 --- /dev/null +++ b/elements/pf-v5-dropdown/demo/disabled.html @@ -0,0 +1,15 @@ + + item1 + item2 + nav item + + +

Interactive content behind the menu should not be activated when a disabled menu item is clicked.

+
Should not toggle behind dropdown +

OOPS! I did it again

+
+ + + diff --git a/elements/pf-v5-dropdown/demo/groups.html b/elements/pf-v5-dropdown/demo/groups.html new file mode 100644 index 0000000000..5d3d7c5406 --- /dev/null +++ b/elements/pf-v5-dropdown/demo/groups.html @@ -0,0 +1,19 @@ + + item4 +
+ + item1 + item2 +
+ item3 +
+ + item1 + item2 + +
+ + + diff --git a/elements/pf-v5-dropdown/demo/index.html b/elements/pf-v5-dropdown/demo/index.html new file mode 100644 index 0000000000..43f718bd9d --- /dev/null +++ b/elements/pf-v5-dropdown/demo/index.html @@ -0,0 +1,10 @@ + + item1 + item2 + item3 + + + + diff --git a/elements/pf-v5-dropdown/docs/pf-v5-dropdown.md b/elements/pf-v5-dropdown/docs/pf-v5-dropdown.md new file mode 100644 index 0000000000..5ee758a6d4 --- /dev/null +++ b/elements/pf-v5-dropdown/docs/pf-v5-dropdown.md @@ -0,0 +1,220 @@ +{% renderInstallation %} {% endrenderInstallation %} + + + +{% renderOverview %} + + Action + Link + Disabled Action + Link +
+ Separated Action +
+ Separated Link +
+{% endrenderOverview %} + +{% band header="Usage" %} + +### Basic + +The following example shows a few different states of dropdown items. As shown in this example, dropdown items may: + +- Use the `disabled` property to disable an item. +- Use the `href` property to make the item a link. + +This example also uses an `hr` to split the menu into 2 sections with a horizontal line. + +{% htmlexample %} + + Action + Link + Disabled Action + Link +
+ Separated Action + Separated Link +
+{% endhtmlexample %} + +### Custom toggle + +A custom toggle can be added using the `toggle` slot. + +{% htmlexample %} + + + My Custom Dropdown + + + Action + Link + Disabled Action + Link +
+ Separated Action + Separated Link +
+{% endhtmlexample %} + +### With kebab toggle + +When there isn't enough space for a labeled button, a slotted toggle button with a kebab icon (three dots) can be used to toggle the dropdown menu open or closed. Make sure to add an `aria-label` to the toggle so that assistive technology users know what the button is. + +{% htmlexample %} + + + + + Action + Link + Disabled Action + Link +
+ Separated Action + Separated Link +
+{% endhtmlexample %} + +### With groups of items + +To group sets of related dropdown items together, use a `pf-v5-dropdown-group`. When more than 1 `pf-v5-dropdown-group` is created in a menu, use the label and labelHeadingLevel properties to assign a name and heading level to each group. + +{% htmlexample %} + + + Action + Link + +
+ + Group 2 action + Group 2 link + +
+ + Group 3 action + Group 3 link + +
+{% endhtmlexample %} + +### With item descriptions + +To provide users with more context about a `pf-v5-dropdown-item`, pass a short message to `description` slot. As shown in the example below, an item's description will appear below its label. + +{% htmlexample %} + + + Action + This is a description + + + Link + This is a very long description that describes the menu item + + + Disabled action + Disabled action description + + + Disabled link + Disabled link description + + +{% endhtmlexample %} + +### Dropdown with icon + +Use the `icon` slot to add an icon. + +{% htmlexample %} + + + + User + + + + Lock + + +{% endhtmlexample %} + +### Disabled dropdown + +Use the `disabled` attribute to disable an entire dropdown. + +{% htmlexample %} + + Action + Link + +{% endhtmlexample %} + +{% endband %} + +{% band header="Accessibility" %} + +The dropdown uses the [Menu Button Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) recommendations from the WAI ARIA [Authoring Best Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg). + +{% renderFile "./docs/_snippets/wai-aria-disabled.md" %} + +#### Menu button + +When focus is on the menu button, the following keyboard interactions apply: + +| Key | Function | +| --------------------- | ------------------------------------------------------------------------------------- | +| Enter | Opens the menu. | +| Space | Opens the menu. | +| Down arrow | Opens the menu and moves focus to the first menu item. | +| Tab | Moves focus out of dropdown element onto the next focusable item and closes menu. | +| Shift+Tab | Moves focus out of dropdown element onto the previous focusable item and closes menu. | + +#### Menu items + +Menu items use the [APG's Roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) recommendation. When focus is on the menu items, the following keyboard interactions apply: + +| Key | Function | +| ---------------------- | --------------------------------------------------------------------------------- | +| Enter | Activates the item and closes the menu. | +| Space | Activates the item and closes the menu. | +| Tab | Moves focus out of dropdown element onto the next focusable item and closes menu. | +| Shift+Tab | Moves focus to the menu button and closes menu. | +| Up Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | +| Down Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | +| Left Arrow | Moves focus to the previous item, optionally wrapping from the first to the last. | +| Right Arrow | Moves focus to the next item, optionally wrapping from the last to the first. | +| Home | Moves focus to the first item in the current menu. | +| End | Moves focus to the last item in the current menu. | +| Escape | Close the menu that contains focus and return focus to the menu button. | + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} +{% renderSlots for="pf-v5-dropdown-item", level=3, header="Slots on `pf-v5-dropdown-item`" %}{% endrenderSlots %} +{% renderSlots for="pf-v5-dropdown-group", level=3, header="Slots on `pf-v5-dropdown-group`" %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} +{% renderAttributes for="pf-v5-dropdown-item", level=3, header="Slots on `pf-v5-dropdown-item`" %}{% endrenderAttributes %} +{% renderAttributes for="pf-v5-dropdown-group", level=3, header="Slots on `pf-v5-dropdown-group`" %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} +{% renderMethods for="pf-v5-dropdown-item", level=3, header="Slots on `pf-v5-dropdown-item`" %}{% endrenderMethods %} +{% renderMethods for="pf-v5-dropdown-group", level=3, header="Slots on `pf-v5-dropdown-group`" %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} +{% renderEvents for="pf-v5-dropdown-item", level=3, header="Slots on `pf-v5-dropdown-item`" %}{% endrenderEvents %} +{% renderEvents for="pf-v5-dropdown-group", level=3, header="Slots on `pf-v5-dropdown-group`" %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} +{% renderCssCustomProperties for="pf-v5-dropdown-item", level=3, header="CSS Properties on `pf-v5-dropdown-item`" %}{% endrenderCssCustomProperties %} +{% renderCssCustomProperties for="pf-v5-dropdown-group", level=3, header="CSS Properties on `pf-v5-dropdown-group`" %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} +{% renderCssParts for="pf-v5-dropdown-item", level=3, header="Parts on `pf-v5-dropdown-item`" %}{% endrenderCssParts %} +{% renderCssParts for="pf-v5-dropdown-group", level=3, header="Parts on `pf-v5-dropdown-group`" %}{% endrenderCssParts %} diff --git a/elements/pf-dropdown/docs/screenshot.png b/elements/pf-v5-dropdown/docs/screenshot.png similarity index 100% rename from elements/pf-dropdown/docs/screenshot.png rename to elements/pf-v5-dropdown/docs/screenshot.png diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown-group.css b/elements/pf-v5-dropdown/pf-v5-dropdown-group.css new file mode 100644 index 0000000000..5bffd9b9e2 --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown-group.css @@ -0,0 +1,38 @@ +:host { + display: block; +} + +[hidden] { + display: none !important; +} + +p { + margin: 0; + font-size: 14px; + font-weight: normal; + color: #777; + padding-top: 0.5rem; + padding-right: 1rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 0.75rem; + font-weight: 400; + color: #6a6e73; +} + +::slotted(hr) { + margin: 0; + border-color: var(--pf-v5-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); + border-style: solid; +} + +::slotted([role="separator"]:not(hr)) { + width: 100%; + height: 1px; + background-color: var(--pf-v5-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); + padding: 0px; + margin: 0px; + border: 0; + display: block; + pointer-events: none; +} diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown-group.ts b/elements/pf-v5-dropdown/pf-v5-dropdown-group.ts new file mode 100644 index 0000000000..65fb2aee4f --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown-group.ts @@ -0,0 +1,38 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import styles from './pf-v5-dropdown-group.css'; + +/** + * Represents a group of items for a dropdown component. + * @slot + * Content for the group of dropdown items + */ +@customElement('pf-v5-dropdown-group') +export class PfV5DropdownGroup extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** + * The label for the group of dropdown items. + */ + @property({ reflect: true }) label?: string; + + render(): TemplateResult<1> { + return html` +

${this.label}

+ + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-dropdown-group': PfV5DropdownGroup; + } +} diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown-item.css b/elements/pf-v5-dropdown/pf-v5-dropdown-item.css new file mode 100644 index 0000000000..32b9958c81 --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown-item.css @@ -0,0 +1,153 @@ +:host { + display: block; + width: 100%; + text-align: left; + white-space: nowrap; + border: none; + cursor: pointer; + /** Dropdown item font size */ + font-size: var( + --pf-v5-c-dropdown__menu-item--FontSize, + var(--pf-global--FontSize--md, 1rem) + ); + /** Dropdown item font weight */ + font-weight: var( + --pf-v5-c-dropdown__menu-item--FontWeight, + var(--pf-global--FontWeight--normal, 400) + ); + /** Dropdown item line height */ + line-height: var( + --pf-v5-c-dropdown__menu-item--LineHeight, + var(--pf-global--LineHeight--md, 1.5) + ); + /** Dropdown item color */ + color: var( + --pf-v5-c-dropdown__menu-item--Color, + var(--pf-global--Color--dark-100, #151515) + ); + /** Dropdown item background color */ + background-color: var( + --pf-v5-c-dropdown__menu-item--BackgroundColor, + transparent + ); + border: 3px solid var( + --pf-v5-c-dropdown__menu-item--BackgroundColor, + transparent + ); +} + +:host([hidden]), +[hidden] { + display: none !important; +} + +:host([disabled]), +.disabled { + &, & a { + cursor: not-allowed; + } + --pf-v5-c-dropdown__menu-item--Color: var( + --pf-v5-c-dropdown__menu-item--disabled--Color, + var(--pf-global--Color--dark-200, #6a6e73) + ); + --pf-v5-c-dropdown__menu-item--BackgroundColor: var( + --pf-v5-c-dropdown__menu-item--disabled--BackgroundColor, + transparent + ); +} + +:host(:hover) { + --pf-v5-c-dropdown__menu-item--Color: var( + --pf-v5-c-dropdown__menu-item--hover--Color, + var(--pf-global--Color--dark-100, #151515) + ); + --pf-v5-c-dropdown__menu-item--BackgroundColor: var( + --pf-v5-c-dropdown__menu-item--hover--BackgroundColor, + var(--pf-global--BackgroundColor--light-300, #f0f0f0) + ); + text-decoration: none; +} + +:host(:focus-within) { + border: 3px solid var(--pf-global--link--Color, #0066cc); +} + +#menuitem { + position: relative; + display: flex; + align-items: stretch; + justify-content: space-between; + flex-wrap: wrap; + /** Dropdown item padding */ + padding: var( + /** Dropdown item padding top */ + --pf-v5-c-dropdown__menu-item--PaddingTop, + var(--pf-global--spacer--sm, 0.5rem) + ) + var( + /** Dropdown item padding right */ + --pf-v5-c-dropdown__menu-item--PaddingRight, + var(--pf-global--spacer--md, 1rem) + ) + var( + /** Dropdown item padding bottom */ + --pf-v5-c-dropdown__menu-item--PaddingBottom, + var(--pf-global--spacer--sm, 0.5rem) + ) + var( + /** Dropdown item padding left */ + --pf-v5-c-dropdown__menu-item--PaddingLeft, + var(--pf-global--spacer--md, 1rem) + ); +} + +#item { + display: flex; + align-items: center; +} + +#item:focus { + outline: none; +} + +#item::after { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +#description { + display: block; + flex: 1 0 100%; + font-size: var( + --pf-v5-c-dropdown__menu-item-description--FontSize, var( + --pf-global--FontSize--sm, 0.75rem + ) + ); + color: var( + --pf-v5-c-dropdown__menu-item-description--Color, var( + --pf-global--Color--dark-200, #6a6e73 + ) + ); + word-break: break-all; +} + +a { + color: var( + --pf-v5-c-dropdown__menu-item--Color, + var(--pf-global--Color--dark-100, #151515) + ) !important; + text-decoration: none !important; +} + +slot:not([name]){ + flex: 1 0 44px; + min-height: 44px; +} + +slot[name="icon"]::slotted(*) { + margin-inline-end: 0.5em; +} diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown-item.ts b/elements/pf-v5-dropdown/pf-v5-dropdown-item.ts new file mode 100644 index 0000000000..5832b44844 --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown-item.ts @@ -0,0 +1,97 @@ +import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { query } from 'lit/decorators/query.js'; +import { consume } from '@lit/context'; + +import { context, type PfV5DropdownContext } from './context.js'; + +import styles from './pf-v5-dropdown-item.css'; + +export class DropdownItemChange extends Event { + constructor() { + super('change', { bubbles: true, cancelable: true }); + } +} + +/** + * Represents an item for a dropdown component. + * @slot icon + * Optional slot for an icon + * @slot description + * Optional slot for item description + * @slot - + * Content for the dropdown item + */ +@customElement('pf-v5-dropdown-item') +export class PfV5DropdownItem extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** + * The value associated with the dropdown item. + * This value can be used to identify the selected item + */ + @property({ reflect: true }) value?: string; + + /** + * href for link dropdown items + */ + @property({ attribute: 'href' }) href?: string; + + /** + * Flag indicating whether the item is active + */ + @property({ type: Boolean, reflect: true }) active = false; + + /** + * Indicates whether the dropdown item is disabled. + * A disabled item cannot be selected. + */ + @property({ type: Boolean, reflect: true }) disabled = false; + + /** Item description; overridden by `description` slot */ + @property() description?: string; + + @consume({ context, subscribe: true }) + @property({ attribute: false }) + private ctx?: PfV5DropdownContext; + + /** @internal */ + @query('#item') menuItem!: HTMLElement; + + protected override updated(changed: PropertyValues): void { + if (changed.has('href')) { + this.dispatchEvent(new DropdownItemChange()); + } + } + + render(): TemplateResult<1> { + const { disabled } = this.ctx ?? { disabled: false }; + const isDisabled = !!this.disabled || !!this.ctx?.disabled; + return html` + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-dropdown-item': PfV5DropdownItem; + } +} diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown-menu.css b/elements/pf-v5-dropdown/pf-v5-dropdown-menu.css new file mode 100644 index 0000000000..5241320477 --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown-menu.css @@ -0,0 +1,37 @@ +:host { + background: var(--pf-v5-c-dropdown__menu--BackgroundColor, #fff); +} + +.disabled { + --pf-v5-c-dropdown__menu-item--Color: var( + --pf-v5-c-dropdown__menu-item--disabled--Color, + var(--pf-global--Color--dark-200, #6a6e73) + ) !important; + --pf-v5-c-dropdown__menu-item--BackgroundColor: var( + --pf-v5-c-dropdown__menu-item--disabled--BackgroundColor, + transparent + ) !important; +} + +:host([hidden]), +[hidden] { + display: none !important; +} + +::slotted(hr) { + margin: 0; + border-color: var(--pf-v5-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); + border-style: solid; +} + +::slotted([role="separator"]:not(hr)) { + width: 100%; + height: 1px; + background-color: var(--pf-v5-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); + padding: 0px; + margin: 0px; + border: 0; + display: block; + pointer-events: none; +} + diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown-menu.ts b/elements/pf-v5-dropdown/pf-v5-dropdown-menu.ts new file mode 100644 index 0000000000..c41721c7cc --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown-menu.ts @@ -0,0 +1,163 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { consume } from '@lit/context'; +import { state } from 'lit/decorators/state.js'; +import { context, type PfV5DropdownContext } from './context.js'; + +import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; + +import { PfV5DropdownItem, DropdownItemChange } from './pf-v5-dropdown-item.js'; +import { PfV5DropdownGroup } from './pf-v5-dropdown-group.js'; + +import styles from './pf-v5-dropdown-menu.css'; +import { classMap } from 'lit/directives/class-map.js'; + +function isDisabledItemClick(event: MouseEvent) { + const item: PfV5DropdownItem | undefined = + event.composedPath().find((x): x is PfV5DropdownItem => x instanceof PfV5DropdownItem); + return !!item?.disabled; +} + +/** + * A **dropdown** presents a menu of actions or links in a constrained space that will trigger a + * process or navigate to a new location. + * @slot - Must contain one or more `` or `` + */ +@customElement('pf-v5-dropdown-menu') +export class PfV5DropdownMenu extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + @consume({ context, subscribe: true }) + @state() + private ctx?: PfV5DropdownContext; + + #internals = InternalsController.of(this, { role: 'menu' }); + + get #items() { + return this.items.map(x => x.menuItem); + } + + #tabindex = RovingTabindexController.of(this, { + getItems: () => this.#items, + }); + + /** + * current active descendant in menu + */ + get activeItem(): HTMLElement | null { + return this.#tabindex.items.at(this.#tabindex.atFocusedItemIndex) + ?? this.#tabindex.atFocusableItems.at(0) + ?? null; + } + + /** + * index of current active descendant in menu + */ + get activeIndex(): number { + return this.#tabindex.atFocusedItemIndex; + } + + get items(): PfV5DropdownItem[] { + return this.#getSlottedItems(this.shadowRoot?.querySelector('slot')); + } + + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener('focusin', this.#onMenuitemFocusin); + this.addEventListener('click', this.#onMenuitemClick); + } + + protected override willUpdate(): void { + this.#internals.ariaDisabled = String(!!this.ctx?.disabled); + } + + render(): TemplateResult<1> { + const { disabled = false } = this.ctx ?? {}; + return html` + + `; + } + + /** + * handles a change event from a dropdown item + * @param event {Event} + */ + #onItemChange(event: Event) { + if (event instanceof DropdownItemChange) { + this.#onSlotChange(); + } + } + + /** + * handles slot change event + */ + #onSlotChange() { + this.#tabindex.items = this.#items; + } + + /** + * handles focusing on an option: + * updates roving tabindex and active descendant + * @param event the focus event + */ + #onMenuitemFocusin(event: FocusEvent) { + if (this.ctx?.disabled) { + event.preventDefault(); + event.stopPropagation(); + } else if (event.target instanceof PfV5DropdownItem) { + this.#focusItem(event.target.menuItem); + } + } + + /** + * handles clicking on a menuitem: + * which selects an item by default + * or toggles selection if multiselectable + * @param event the click event + */ + #onMenuitemClick(event: MouseEvent) { + if (this.ctx?.disabled || isDisabledItemClick(event)) { + event.preventDefault(); + event.stopPropagation(); + } else if (event.target instanceof PfV5DropdownItem) { + this.#focusItem(event.target.menuItem); + } + } + + #focusItem(item: HTMLElement) { + const itemIndex = this.#tabindex.items.indexOf(item); + if (itemIndex !== this.#tabindex.atFocusedItemIndex) { + this.#tabindex.atFocusedItemIndex = itemIndex; + } + } + + #getSlottedItems(slot?: HTMLSlotElement | null): PfV5DropdownItem[] { + return slot + ?.assignedElements() + .flatMap(element => { + if (element instanceof HTMLSlotElement) { + return this.#getSlottedItems(element); + } else if (element instanceof PfV5DropdownItem) { + return [element]; + } else if (element instanceof PfV5DropdownGroup) { + return Array.from(element.querySelectorAll('pf-v5-dropdown-item')); + } else { + return []; + } + }) ?? []; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-dropdown-menu': PfV5DropdownMenu; + } +} diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown.css b/elements/pf-v5-dropdown/pf-v5-dropdown.css new file mode 100644 index 0000000000..44a743ba33 --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown.css @@ -0,0 +1,351 @@ +:host { + display: inline-block; + position: relative; + --pf-v5-c-dropdown__toggle--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); + --pf-v5-c-dropdown__toggle--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); + --pf-v5-c-dropdown__toggle--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--MinWidth: 0; + --pf-v5-c-dropdown__toggle--FontSize: var(--pf-global--FontSize--md, 1rem); + --pf-v5-c-dropdown__toggle--FontWeight: var(--pf-global--FontWeight--normal, 400); + --pf-v5-c-dropdown__toggle--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-dropdown__toggle--LineHeight: var(--pf-global--LineHeight--md, 1.5); + --pf-v5-c-dropdown__toggle--BackgroundColor: transparent; + --pf-v5-c-dropdown__toggle--before--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-dropdown__toggle--before--BorderTopColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-dropdown__toggle--before--BorderRightColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-dropdown__toggle--before--BorderBottomColor: var(--pf-global--BorderColor--200, #8a8d90); + --pf-v5-c-dropdown__toggle--before--BorderLeftColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-dropdown__toggle--hover--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-dropdown__toggle--focus--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--focus--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-dropdown__toggle--active--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--active--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-dropdown--m-expanded__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown--m-expanded__toggle--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-dropdown__toggle--disabled--BackgroundColor: var(--pf-global--disabled-color--300, #f0f0f0); + --pf-v5-c-dropdown__toggle--m-plain--Color: var(--pf-global--Color--200, #6a6e73); + --pf-v5-c-dropdown__toggle--m-plain--hover--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-dropdown__toggle--m-plain--disabled--Color: var(--pf-global--disabled-color--200, #d2d2d2); + --pf-v5-c-dropdown__toggle--m-plain--PaddingRight: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-dropdown__toggle--m-plain--PaddingLeft: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-dropdown__toggle--m-plain--child--LineHeight: normal; + --pf-v5-c-dropdown__toggle--m-primary--Color: var(--pf-global--Color--light-100, #fff); + --pf-v5-c-dropdown__toggle--m-primary--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + --pf-v5-c-dropdown__toggle--m-primary--BackgroundColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-primary--hover--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-primary--focus--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-primary--active--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown--m-expanded__toggle--m-primary--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-primary--disabled--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-dropdown__toggle--m-secondary--Color: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-secondary--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + --pf-v5-c-dropdown__toggle--m-secondary--BackgroundColor: transparent; + --pf-v5-c-dropdown__toggle--m-secondary--before--BorderWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-dropdown__toggle--m-secondary--hover--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--focus--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--active--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--before--BorderColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-secondary--hover--before--BorderColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-secondary--focus--before--BorderColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-secondary--active--before--BorderColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown--m-expanded__toggle--m-secondary--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown--m-expanded__toggle--m-secondary--before--BorderColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle-button--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-dropdown__toggle-progress--Visibility: hidden; + --pf-v5-c-dropdown__toggle-progress--c-spinner--diameter: var(--pf-global--FontSize--sm, 0.875rem); + --pf-v5-c-dropdown__toggle--m-split-button--child--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); + --pf-v5-c-dropdown__toggle--m-split-button--child--PaddingRight: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-dropdown__toggle--m-split-button--child--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); + --pf-v5-c-dropdown__toggle--m-split-button--child--PaddingLeft: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-dropdown__toggle--m-split-button--child--BackgroundColor: transparent; + --pf-v5-c-dropdown__toggle--m-split-button--first-child--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--m-split-button--last-child--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--m-split-button--m-action--child--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--m-split-button--m-action--child--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--m-split-button--m-action__toggle-button--MarginRight: calc(-1 * var(--pf-global--BorderWidth--sm, 1px)); + --pf-v5-c-dropdown__toggle--m-split-button__toggle-check__input--TranslateY: -0.0625rem; + --pf-v5-c-dropdown__toggle--m-split-button__toggle-text--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--m-split-button--child--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--child--BorderRadius: var(--pf-v5-c-dropdown__toggle--m-split-button--child--BorderRadius); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--child--BackgroundColor: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--child--hover--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--child--focus--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--child--active--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--child--m-expanded--BackgroundColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--m-action--child--BorderLeftColor: var(--pf-global--primary-color--200, #004080); + --pf-v5-c-dropdown__toggle--m-split-button--m-primary--m-action--child--BorderLeftWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-dropdown--m-expanded__toggle--m-secondary--m-split-button--child--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--Color: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--hover--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--focus--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--active--before--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderWidth--base: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderColor--base: var(--pf-global--primary-color--100, #06c); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderColor: var(--pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderColor--base); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderWidth: var(--pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--BorderWidth--base); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--hover--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--focus--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle--m-secondary--m-split-button--child--before--active--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-dropdown__toggle-icon--LineHeight: var(--pf-global--LineHeight--md, 1.5); + --pf-v5-c-dropdown__toggle-icon--MarginRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle-icon--MarginLeft: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-dropdown--m-top--m-expanded__toggle-icon--Rotate: 180deg; + --pf-v5-c-dropdown--m-plain__toggle-icon--Color: var(--pf-global--Color--200, #6a6e73); + --pf-v5-c-dropdown--m-plain--hover__toggle-icon--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-dropdown__menu--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #fff); + /** Dropdown box shadow */ + --pf-v5-c-dropdown__menu--BoxShadow: var(--pf-global--BoxShadow--md, 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06)); + /** Dropdown top padding */ + --pf-v5-c-dropdown__menu--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__menu--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); + /** Dropdown top position */ + --pf-v5-c-dropdown__menu--Top: calc(100% + var(--pf-global--spacer--xs, 0.25rem)); + /** Dropdown z-index */ + --pf-v5-c-dropdown__menu--ZIndex: var(--pf-global--ZIndex--sm, 200); + --pf-v5-c-dropdown--m-top__menu--Top: 0; + --pf-v5-c-dropdown--m-top__menu--TranslateY: calc(-100% - var(--pf-global--spacer--xs, 0.25rem)); + --pf-v5-c-dropdown__menu-item--BackgroundColor: transparent; + --pf-v5-c-dropdown__menu-item--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); + /** Dropdown menu item right padding */ + --pf-v5-c-dropdown__menu-item--PaddingRight: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-dropdown__menu-item--PaddingBottom: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__menu-item--PaddingLeft: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-dropdown__menu-item--FontSize: var(--pf-global--FontSize--md, 1rem); + --pf-v5-c-dropdown__menu-item--FontWeight: var(--pf-global--FontWeight--normal, 400); + --pf-v5-c-dropdown__menu-item--LineHeight: var(--pf-global--LineHeight--md, 1.5); + --pf-v5-c-dropdown__menu-item--Color: var(--pf-global--Color--dark-100, #151515); + --pf-v5-c-dropdown__menu-item--hover--Color: var(--pf-global--Color--dark-100, #151515); + --pf-v5-c-dropdown__menu-item--disabled--Color: var(--pf-global--Color--dark-200, #6a6e73); + --pf-v5-c-dropdown__menu-item--hover--BackgroundColor: var(--pf-global--BackgroundColor--light-300, #f0f0f0); + --pf-v5-c-dropdown__menu-item--disabled--BackgroundColor: transparent; + --pf-v5-c-dropdown__menu-item--m-text--Color: var(--pf-global--Color--dark-200, #6a6e73); + --pf-v5-c-dropdown__menu-item-icon--MarginRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__menu-item-icon--Width: var(--pf-global--icon--FontSize--lg, 1.5rem); + --pf-v5-c-dropdown__menu-item-icon--Height: var(--pf-global--icon--FontSize--lg, 1.5rem); + --pf-v5-c-dropdown__menu-item-description--FontSize: var(--pf-global--FontSize--xs, 0.75rem); + --pf-v5-c-dropdown__menu-item-description--Color: var(--pf-global--Color--dark-200, #6a6e73); + --pf-v5-c-dropdown__group--group--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__group-title--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__group-title--PaddingRight: var(--pf-v5-c-dropdown__menu-item--PaddingRight); + --pf-v5-c-dropdown__group-title--PaddingBottom: var(--pf-v5-c-dropdown__menu-item--PaddingBottom); + --pf-v5-c-dropdown__group-title--PaddingLeft: var(--pf-v5-c-dropdown__menu-item--PaddingLeft); + --pf-v5-c-dropdown__group-title--FontSize: var(--pf-global--FontSize--xs, 0.75rem); + --pf-v5-c-dropdown__group-title--FontWeight: var(--pf-global--FontWeight--normal, 400); + --pf-v5-c-dropdown__group-title--Color: var(--pf-global--Color--dark-200, #6a6e73); + --pf-v5-c-dropdown__toggle-image--MarginTop: 0; + --pf-v5-c-dropdown__toggle-image--MarginBottom: 0; + --pf-v5-c-dropdown__toggle-image--MarginRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown--c-divider--MarginTop: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown--c-divider--MarginBottom: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-dropdown__toggle--c-badge__toggle-icon--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-dropdown__toggle--c-badge__toggle-icon--MarginRight: 0; + --pf-v5-c-dropdown--c-menu--Top: calc(100% + var(--pf-global--spacer--xs, 0.25rem)); + --pf-v5-c-dropdown--c-menu--ZIndex: var(--pf-global--ZIndex--sm, 200); + --pf-v5-c-dropdown--m-top--c-menu--Top: 0; + --pf-v5-c-dropdown--m-top--c-menu--TranslateY: calc(-100% - var(--pf-global--spacer--xs, 0.25rem)); + --pf-v5-c-dropdown--m-full-height__toggle--before--BorderTopWidth: 0; + --pf-v5-c-dropdown--m-full-height__toggle--expanded--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); + --pf-v5-c-dropdown--m-full-height__toggle--hover--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); + --pf-v5-c-dropdown--m-full-height__toggle--active--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); + --pf-v5-c-dropdown--m-full-height__toggle--focus--before--BorderBottomWidth: var(--pf-global--BorderWidth--xl, 4px); + --pf-v5-c-dropdown--m-full-height__toggle--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-dropdown--m-full-height__toggle--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); + --_font-size: var( + --pf-v5-c-dropdown__menu-item--FontSize, + var(--pf-global--FontSize--md, 1rem) + ); + --_font-weight: var( + --pf-v5-c-dropdown__menu-item--FontWeight, + var(--pf-global--FontWeight--normal, 400) + ); + --_line-height: var( + --pf-v5-c-dropdown__menu-item--LineHeight, + var(--pf-global--LineHeight--md, 1.5) + ); + --_color: var( + --pf-v5-c-dropdown__menu-item--Color, + var(--pf-global--Color--dark-100, #151515) + ); + --_background-color: var( + --pf-v5-c-dropdown__menu-item--BackgroundColor, + transparent + ); +} + +:host([disabled]) { + color: var(--pf-global--Color--dark-200, #6a6e73); +} + +:host([hidden]), +[hidden] { + display: none !important; +} + +slot[name="toggle"] { + cursor: pointer; +} + +pf-v5-button#default-toggle, +::slotted([slot="toggle"]) { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + min-width: var(--pf-v5-c-dropdown__toggle--MinWidth); + max-width: 100%; + padding: + var(--pf-v5-c-dropdown__toggle--PaddingTop) + var(--pf-v5-c-dropdown__toggle--PaddingRight) + var(--pf-v5-c-dropdown__toggle--PaddingBottom) + var(--pf-v5-c-dropdown__toggle--PaddingLeft); + font-size: var(--pf-v5-c-dropdown__toggle--FontSize); + font-weight: var(--pf-v5-c-dropdown__toggle--FontWeight); + line-height: var(--pf-v5-c-dropdown__toggle--LineHeight); + color: var(--pf-v5-c-dropdown__toggle--Color); + background-color: var(--pf-v5-c-dropdown__toggle--BackgroundColor); + border: none; +} + +pf-v5-dropdown-menu, +::slotted(pf-v5-dropdown-menu) { + position: absolute !important; + top: var( + --pf-v5-c-dropdown__menu--Top, + calc(100% + var(--pf-global--spacer--xs, 0.25rem)) + ) !important; + left: var( + --pf-v5-c-dropdown__menu--Left, + 0 + ) !important; + right: var( + --pf-v5-c-dropdown__menu--Right, + auto + ) !important; + z-index: var( + --pf-v5-c-dropdown__menu--ZIndex, + var(--pf-global--ZIndex--sm, 200) + ) !important; + padding-top: var( + --pf-v5-c-dropdown__menu--PaddingTop, + var(--pf-global--spacer--sm, 0.5rem) + ) !important; + padding-bottom: var( + --pf-v5-c-dropdown__menu--PaddingBottom, + var(--pf-global--spacer--sm, 0.5rem) + ) !important; + background: var(--pf-v5-c-dropdown__menu--BackgroundColor, #fff) !important; + background-clip: padding-box !important; + box-shadow: var( + --pf-v5-c-dropdown__menu--BoxShadow, + var( + --pf-global--BoxShadow--md, + 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), + 0 0 0.25rem 0 rgba(3, 3, 3, 0.06) + ) + ) !important; + min-width: 100% !important; + margin: 0 !important; +} + +pf-v5-button svg { + width: 1em; + height: 1em; + flex: 0 0 auto; + margin-inline-start: 1em; +} + +::slotted(hr) { + margin: 0; + border-color: var(--pf-v5-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); + border-style: solid; +} + +::slotted([role="separator"]:not(hr)) { + width: 100%; + height: 1px; + background-color: var(--pf-v5-c-divider--BackgroundColor, var(--pf-global--BorderColor--100, #d2d2d2)); + padding: 0px; + margin: 0px; + border: 0; + display: block; +} + +.disabled { + & pf-v5-dropdown-menu, + & ::slotted(pf-v5-dropdown-menu) { + background-color: var(--pf-v5-theme--color--surface--lighter, #f0f0f0) !important; + cursor: not-allowed !important; + } + & pf-v5-button#default-toggle, + & ::slotted([slot="toggle"]) { + --pf-v5-c-button--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--disabled--after--BorderColor); + + --pf-v5-c-button--m-primary--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-primary--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + + --pf-v5-c-button--m-secondary--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-secondary--focus--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-secondary--hover--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-secondary--active--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-secondary--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-secondary--after--BorderColor: transparent; + --pf-v5-c-button--m-secondary--hover--after--BorderColor: transparent; + --pf-v5-c-button--m-secondary--focus--after--BorderColor: transparent; + --pf-v5-c-button--m-secondary--active--after--BorderColor: transparent; + --pf-v5-c-button--m-secondary--focus--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-secondary--hover--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-secondary--active--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-secondary--focus--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + + --pf-v5-c-button--m-control--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-control--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-control--focus--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-control--hover--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-control--active--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-control--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-control--after--BorderColor: transparent; + --pf-v5-c-button--m-control--hover--after--BorderColor: transparent; + --pf-v5-c-button--m-control--focus--after--BorderColor: transparent; + --pf-v5-c-button--m-control--active--after--BorderColor: transparent; + --pf-v5-c-button--m-control--focus--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-control--hover--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-control--active--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-control--focus--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-control--after--BorderBottomColor: transparent; + --pf-v5-c-button--m-control--focus--after--BorderBottomColor: transparent; + --pf-v5-c-button--m-control--hover--after--BorderBottomColor: transparent; + --pf-v5-c-button--m-control--active--after--BorderBottomColor: transparent; + + --pf-v5-c-button--m-plain--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-plain--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-plain--focus--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-plain--hover--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-plain--active--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-plain--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-plain--after--BorderColor: transparent; + --pf-v5-c-button--m-plain--hover--after--BorderColor: transparent; + --pf-v5-c-button--m-plain--focus--after--BorderColor: transparent; + --pf-v5-c-button--m-plain--active--after--BorderColor: transparent; + --pf-v5-c-button--m-plain--focus--Color: var(--pf-v5-c-button--disabled--Color); + --pf-v5-c-button--m-plain--hover--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-plain--active--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + --pf-v5-c-button--m-plain--focus--BackgroundColor: var(--pf-v5-c-button--disabled--BackgroundColor); + } + + & pf-v5-button:focus, + & ::slotted([slot="toggle"]:focus) { + outline: 3px solid var(--pf-global--link--Color, #0066cc); + } +} + +pf-v5-button#default-toggle, +::slotted(pf-v5-button[variant="control"]) { + --_button-icon-vertical-align: -0.25em; + --_button-icon-padding-inline-start: var(--pf-v5-c-dropdown__toggle--PaddingLeft, + var(--pf-global--spacer--md, 1rem)); + --_button-icon-padding-inline-end: var(-pf-v5-c-dropdown__toggle--PaddingRight, + var(--pf-global--spacer--sm, 0.5rem)); +} diff --git a/elements/pf-v5-dropdown/pf-v5-dropdown.ts b/elements/pf-v5-dropdown/pf-v5-dropdown.ts new file mode 100644 index 0000000000..af58ad54b0 --- /dev/null +++ b/elements/pf-v5-dropdown/pf-v5-dropdown.ts @@ -0,0 +1,258 @@ +import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; +import { styleMap } from 'lit/directives/style-map.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { queryAssignedElements } from 'lit/decorators/query-assigned-elements.js'; +import { provide } from '@lit/context'; + +import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; +import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; +import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; + +import { context, type PfV5DropdownContext } from './context.js'; +import { PfV5DropdownItem } from './pf-v5-dropdown-item.js'; +import { PfV5DropdownMenu } from './pf-v5-dropdown-menu.js'; + +import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; + +import styles from './pf-v5-dropdown.css'; + + +export class PfV5DropdownSelectEvent extends Event { + constructor( + public originalEvent: Event | KeyboardEvent, + public value: string + ) { + super('select', { bubbles: true, cancelable: true }); + } +} + +/** + * A **dropdown** presents a menu of actions or links in a constrained space that + * will trigger a process or navigate to a new location. + * @alias Dropdown + * @fires {PfV5DropdownSelectEvent} select - when a user select dropdown value + * @fires open - when the dropdown toggles open + * @fires close - when the dropdown toggles closed + */ +@customElement('pf-v5-dropdown') +export class PfV5Dropdown extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** + * When disabled, the dropdown can still be toggled open and closed via keyboard, but menu items cannot be activated. + */ + @property({ type: Boolean, reflect: true }) disabled = false; + + /** + * Whether the dropdown is expanded + */ + @property({ type: Boolean, reflect: true }) expanded = false; + + @queryAssignedElements({ slot: 'toggle', flatten: true }) + private _toggleElements!: HTMLElement[]; + + @queryAssignedElements({ slot: 'menu', flatten: true }) + private _menuElements!: HTMLElement[]; + + @provide({ context }) private ctx: PfV5DropdownContext = { disabled: false }; + + #logger = new Logger(this); + + #float = new FloatingDOMController(this, { + content: () => this._menuElements?.at(0), + }); + + protected override async getUpdateComplete(): Promise { + const ps = await Promise.all([ + super.getUpdateComplete(), + this._menuElements?.map(x => (x as LitElement).updateComplete), + ]); + return ps.every(x=>!!x); + } + + willUpdate(changed: PropertyValues): void { + if (changed.has('disabled')) { + const { disabled } = this; + this.ctx = { disabled }; + } + } + + render(): TemplateResult<1> { + const { expanded } = this; + const { anchor, alignment, styles = {} } = this.#float; + const { disabled } = this; + return html` +
+ + + + Dropdown + + + + + + + + + + + +
`; + } + + override firstUpdated(): void { + this.#onSlotchange(); + } + + updated(changed: PropertyValues): void { + if (changed.has('expanded')) { + this.#expandedChanged(); + } + if (changed.has('disabled')) { + this.#disabledChanged(); + } + } + + #validateDOM() { + const [toggle] = this._toggleElements; + const [menu] = this._menuElements; + if (!toggle) { + this.#logger.warn('no toggle found'); + return false; + } else if (!menu) { + this.#logger.warn('no menu found'); + return false; + } else if (![toggle, menu].map(x => this.shadowRoot?.contains(x)) + .every((p, _, a) => p === a[0])) { + this.#logger.warn('toggle and menu must be located in the same root'); + return false; + } else { + return true; + } + } + + #onSlotchange() { + if (this.#validateDOM()) { + const [menu] = this._menuElements; + const [toggle] = this._toggleElements; + menu.id ||= getRandomId('menu'); + toggle.setAttribute('aria-controls', menu.id); + toggle.setAttribute('aria-haspopup', menu.id); + toggle.setAttribute('aria-expanded', String(this.expanded) as 'true' | 'false'); + } + } + + async #expandedChanged() { + const will = this.expanded ? 'close' : 'open'; + const [menu] = this._menuElements; + const [toggle] = this._toggleElements; + toggle.setAttribute('aria-expanded', `${String(this.expanded) as 'true' | 'false'}`); + this.dispatchEvent(new Event(will)); + if (this.expanded) { + await this.#float.show(); + if (menu instanceof PfV5DropdownMenu) { + menu.activeItem?.focus(); + } + } else { + await this.#float.hide(); + } + } + + #disabledChanged() { + if (this.#validateDOM()) { + const [toggle] = this._toggleElements; + toggle.setAttribute('aria-disabled', String(!!this.disabled)); + } + } + + #onSelect(event: KeyboardEvent | Event & { target: PfV5DropdownItem }) { + const [menu] = this._menuElements; + if (menu instanceof PfV5DropdownMenu) { + const target = event.target as PfV5DropdownItem || menu.activeItem; + this.dispatchEvent(new PfV5DropdownSelectEvent(event, `${target?.value}`)); + this.hide(); + } + } + + #onButtonKeydown(event: KeyboardEvent) { + switch (event.key) { + case 'ArrowDown': { + this.show(); + } + } + } + + #onMenuFocusout(event: FocusEvent) { + if (this.expanded) { + const root = this.getRootNode(); + const [menu] = this._menuElements; + if (root instanceof ShadowRoot + || root instanceof Document + && event.relatedTarget instanceof PfV5DropdownItem + && menu instanceof PfV5DropdownMenu + && !menu.items.includes(event.relatedTarget) + ) { + this.hide(); + } + } + } + + #onMenuKeydown(event: KeyboardEvent) { + switch (event.key) { + case 'Enter': + case ' ': + event.preventDefault(); + this.#onSelect(event); + break; + case 'Escape': + this.hide(); + this._toggleElements?.at(0)?.focus(); + } + } + + /** + * Opens the dropdown + */ + async show(): Promise { + this.expanded = true; + await this.updateComplete; + } + + /** + * Closes the dropdown + */ + async hide(): Promise { + this.expanded = false; + await this.updateComplete; + } + + async toggle(): Promise { + this.expanded = !this.expanded; + await this.updateComplete; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-dropdown': PfV5Dropdown; + } +} diff --git a/elements/pf-dropdown/test/pf-dropdown.e2e.ts b/elements/pf-v5-dropdown/test/pf-dropdown.e2e.ts similarity index 95% rename from elements/pf-dropdown/test/pf-dropdown.e2e.ts rename to elements/pf-v5-dropdown/test/pf-dropdown.e2e.ts index 97435b0d68..4d2d41ef9b 100644 --- a/elements/pf-dropdown/test/pf-dropdown.e2e.ts +++ b/elements/pf-v5-dropdown/test/pf-dropdown.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-dropdown'; +const tagName = 'pf-v5-dropdown'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-dropdown/test/pf-dropdown.spec.ts b/elements/pf-v5-dropdown/test/pf-dropdown.spec.ts similarity index 87% rename from elements/pf-dropdown/test/pf-dropdown.spec.ts rename to elements/pf-v5-dropdown/test/pf-dropdown.spec.ts index 39aac0b05e..9d7c9ffbdf 100644 --- a/elements/pf-dropdown/test/pf-dropdown.spec.ts +++ b/elements/pf-v5-dropdown/test/pf-dropdown.spec.ts @@ -2,7 +2,7 @@ import { expect, html } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; import { sendKeys, resetMouse } from '@web/test-runner-commands'; import { clickElementAtCenter } from '@patternfly/pfe-tools/test/utils.js'; -import { PfDropdown } from '@patternfly/elements/pf-dropdown/pf-dropdown.js'; +import { PfV5Dropdown } from '@patternfly/elements/pf-v5-dropdown/pf-v5-dropdown.js'; import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; function press(key: string) { @@ -11,33 +11,33 @@ function press(key: string) { }; } -describe('', function() { - let element: PfDropdown; +describe('', function() { + let element: PfV5Dropdown; const updateComplete = () => element.updateComplete; describe('simply instantiating', function() { it('imperatively instantiates', function() { - expect(document.createElement('pf-dropdown')).to.be.an.instanceof(PfDropdown); + expect(document.createElement('pf-v5-dropdown')).to.be.an.instanceof(PfV5Dropdown); }); it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-dropdown'); + element = await createFixture(html``); + const klass = customElements.get('pf-v5-dropdown'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfDropdown); + .to.be.an.instanceOf(PfV5Dropdown); }); }); describe('with default toggle button', function() { beforeEach(async function() { - element = await createFixture(html` - - item 1 - item 2 - + element = await createFixture(html` + + item 1 + item 2 + `); }); diff --git a/elements/pf-v5-helper-text/demo/dynamic-list.html b/elements/pf-v5-helper-text/demo/dynamic-list.html new file mode 100644 index 0000000000..cd883df086 --- /dev/null +++ b/elements/pf-v5-helper-text/demo/dynamic-list.html @@ -0,0 +1,30 @@ + +
    +
  • + Must be at least 14 characters +
  • +
  • + Cannot contain any variation of the word "redhat" +
  • +
  • + Must include at least 3 of the following: lowercase letter, uppercase letters, numbers, symbols +
  • +
+
+ + + + diff --git a/elements/pf-v5-helper-text/demo/dynamic.html b/elements/pf-v5-helper-text/demo/dynamic.html new file mode 100644 index 0000000000..920ec038c4 --- /dev/null +++ b/elements/pf-v5-helper-text/demo/dynamic.html @@ -0,0 +1,42 @@ + + This is default helper text + + + + This is indeterminate helper text + + + + This is warning helper text + + + + This is success helper text + + + + This is error helper text + + + + + diff --git a/elements/pf-v5-helper-text/demo/index.html b/elements/pf-v5-helper-text/demo/index.html new file mode 100644 index 0000000000..2e06189fb7 --- /dev/null +++ b/elements/pf-v5-helper-text/demo/index.html @@ -0,0 +1 @@ +Success! diff --git a/elements/pf-v5-helper-text/demo/multiple.html b/elements/pf-v5-helper-text/demo/multiple.html new file mode 100644 index 0000000000..b698d9c78b --- /dev/null +++ b/elements/pf-v5-helper-text/demo/multiple.html @@ -0,0 +1,11 @@ + +
    +
  • This is default helper text
  • +
  • This is another default helper text in the same block
  • +
  • And this is more default text in the same block
  • +
+
+ + diff --git a/elements/pf-v5-helper-text/demo/static-icons.html b/elements/pf-v5-helper-text/demo/static-icons.html new file mode 100644 index 0000000000..8af0bcf24c --- /dev/null +++ b/elements/pf-v5-helper-text/demo/static-icons.html @@ -0,0 +1,37 @@ + + This is default helper text + + + + This is indeterminate helper text + + + + This is warning helper text + + + + This is success helper text + + + + This is error helper text + + + + + diff --git a/elements/pf-v5-helper-text/demo/static.html b/elements/pf-v5-helper-text/demo/static.html new file mode 100644 index 0000000000..ffee3e1851 --- /dev/null +++ b/elements/pf-v5-helper-text/demo/static.html @@ -0,0 +1,16 @@ +This is default helper text +This is indeterminate helper text +This is warning helper text +This is success helper text +This is error helper text + + + + diff --git a/elements/pf-v5-helper-text/docs/pf-v5-helper-text.md b/elements/pf-v5-helper-text/docs/pf-v5-helper-text.md new file mode 100644 index 0000000000..e9ffe85f78 --- /dev/null +++ b/elements/pf-v5-helper-text/docs/pf-v5-helper-text.md @@ -0,0 +1,17 @@ +{% renderOverview %} + +{% endrenderOverview %} + +{% band header="Usage" %}{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-helper-text/pf-v5-helper-text.css b/elements/pf-v5-helper-text/pf-v5-helper-text.css new file mode 100644 index 0000000000..a0c85dd861 --- /dev/null +++ b/elements/pf-v5-helper-text/pf-v5-helper-text.css @@ -0,0 +1,35 @@ +:host { + display: flex; + align-items: center; + gap: var(--pf-v5-c-helper-text--Gap, 0.25rem); + font-size: var(--pf-v5-c-helper-text--FontSize, 0.875rem); + color: var(--pf-v5-c-helper-text__item-text--Color, #151515); + line-height: 1.4; +} + +/* Color variants */ +:host([variant='indeterminate']) { + color: var(--pf-v5-c-helper-text__item-text--m-indeterminate--Color, #6a6e73); +} + +:host([variant='warning']) { + color: var(--pf-v5-c-helper-text__item-text--m-warning--Color, #795600); +} + +:host([variant='success']) { + color: var(--pf-v5-c-helper-text__item-text--m-success--Color, #1e4f18); +} + +:host([variant='error']) { + color: var(--pf-v5-c-helper-text__item-text--m-error--Color, #a30000); +} + +::slotted(ul) { + margin: 0; + padding: 0; + list-style-type: none; +} + +pf-v5-icon { + fill: currentColor; +} diff --git a/elements/pf-v5-helper-text/pf-v5-helper-text.ts b/elements/pf-v5-helper-text/pf-v5-helper-text.ts new file mode 100644 index 0000000000..ca46eb51b7 --- /dev/null +++ b/elements/pf-v5-helper-text/pf-v5-helper-text.ts @@ -0,0 +1,96 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; + +import styles from './pf-v5-helper-text.css'; + +/** Map of status to default icons (Font Awesome solid set). */ +const StatusIconMap = { + success: 'check-circle', + warning: 'exclamation-triangle', + error: 'exclamation-circle', + indeterminate: 'info-circle', +}; + +/** + * Displays contextual feedback for form fields with optional icon and status color. + * + * @alias Helper Text + * @slot icon - Optional custom icon to override the default icon. + * @slot - Default slot for the helper text content. + * + * @fires icon-load - Fired when the icon successfully loads. + * @fires icon-error - Fired if loading the icon fails. + * + * @csspart icon - The container for the icon. + * @csspart text - The container for the text. + */ +@customElement('pf-v5-helper-text') +export class PfV5HelperText extends LitElement { + public static readonly styles: CSSStyleSheet[] = [styles]; + + /** + * Defines the helper text status and its corresponding color and icon. + */ + @property({ reflect: true }) variant: + | 'default' + | 'success' + | 'warning' + | 'error' + | 'indeterminate' = 'default'; + + /** + * Custom icon name to override the default icon. + * Requires `` to be imported. + */ + @property() icon?: string; + + /** + * Icon set for custom icons (e.g., 'fas', 'patternfly'). + */ + @property({ attribute: 'icon-set' }) iconSet?: string; + + #slots = new SlotController(this, 'icon', null); + + /** + * Determine the effective icon to display. + */ + private get _resolvedIcon(): string | undefined { + if (this.icon) { + return this.icon; + } + if (this.variant !== 'default') { + return StatusIconMap[this.variant]; + } + return undefined; + } + + protected render(): TemplateResult<1> { + const iconName = this._resolvedIcon; + const showIcon = !!this.icon || this.#slots.hasSlotted('icon'); + + return html` + + + + + + + + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-helper-text': PfV5HelperText; + } +} diff --git a/elements/pf-helper-text/test/pf-helper-text.e2e.ts b/elements/pf-v5-helper-text/test/pf-helper-text.e2e.ts similarity index 95% rename from elements/pf-helper-text/test/pf-helper-text.e2e.ts rename to elements/pf-v5-helper-text/test/pf-helper-text.e2e.ts index 3eec449fd4..ab593d7b37 100644 --- a/elements/pf-helper-text/test/pf-helper-text.e2e.ts +++ b/elements/pf-v5-helper-text/test/pf-helper-text.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-helper-text'; +const tagName = 'pf-v5-helper-text'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-helper-text/test/pf-helper-text.spec.ts b/elements/pf-v5-helper-text/test/pf-helper-text.spec.ts new file mode 100644 index 0000000000..fb3d24f7b7 --- /dev/null +++ b/elements/pf-v5-helper-text/test/pf-helper-text.spec.ts @@ -0,0 +1,64 @@ +import { expect, html } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { PfV5HelperText } from '@patternfly/elements/pf-v5-helper-text/pf-v5-helper-text.js'; + +describe('', function() { + describe('simply instantiating', function() { + let element: PfV5HelperText; + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-helper-text')).to.be.an.instanceof(PfV5HelperText); + }); + + it('should upgrade', async function() { + element = await createFixture(html``); + const klass = customElements.get('pf-v5-helper-text'); + expect(element) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV5HelperText); + }); + }); + + describe('variant property', function() { + it('should default to "default" variant', async function() { + const element = await createFixture(html``); + expect(element.variant).to.equal('default'); + }); + + it('should reflect variant attribute', async function() { + const element = await createFixture(html``); + expect(element.variant).to.equal('success'); + expect(element.getAttribute('variant')).to.equal('success'); + }); + }); + + describe('icon display', function() { + it('should show icon when icon property is set', async function() { + const element = await createFixture(html`Success`); + await element.updateComplete; + const iconContainer = element.shadowRoot?.querySelector('#icon'); + expect(iconContainer?.hasAttribute('hidden')).to.be.false; + }); + + it('should hide icon when no icon or slotted icon is present', async function() { + const element = await createFixture(html`Text`); + await element.updateComplete; + const iconContainer = element.shadowRoot?.querySelector('#icon'); + expect(iconContainer?.hasAttribute('hidden')).to.be.true; + }); + }); + + describe('text content', function() { + it('should render text content in default slot', async function() { + const element = await createFixture(html`Helper text content`); + expect(element.textContent?.trim()).to.equal('Helper text content'); + }); + + it('should have aria-live on text container', async function() { + const element = await createFixture(html`Text`); + await element.updateComplete; + const textContainer = element.shadowRoot?.querySelector('#text'); + expect(textContainer?.getAttribute('aria-live')).to.equal('polite'); + }); + }); +}); diff --git a/elements/pf-v5-hint/demo/index.html b/elements/pf-v5-hint/demo/index.html new file mode 100644 index 0000000000..e89b899bd9 --- /dev/null +++ b/elements/pf-v5-hint/demo/index.html @@ -0,0 +1,47 @@ +

Default with no title

+ + Welcome to the new documentation experience. + Learn more about the improved features. + + + + + + + Link + Action + Disabled Link + Disabled Action +
+ Separated Link + Separated Action +
+
+ Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. + Try it for 90 days +
+ + + + diff --git a/elements/pf-v5-hint/demo/with-title.html b/elements/pf-v5-hint/demo/with-title.html new file mode 100644 index 0000000000..277cd71c55 --- /dev/null +++ b/elements/pf-v5-hint/demo/with-title.html @@ -0,0 +1,59 @@ +

Hint with title

+ + + + + + Link + Action + Disabled Link + Disabled Action +
+ Separated Link + Separated Action +
+
+ Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. +
+ + + + + + Link + Action + Disabled Link + Disabled Action +
+ Separated Link + Separated Action +
+
+ Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. + Try it for 90 days +
+ + + + diff --git a/elements/pf-v5-hint/docs/pf-v5-hint.md b/elements/pf-v5-hint/docs/pf-v5-hint.md new file mode 100644 index 0000000000..414ddec562 --- /dev/null +++ b/elements/pf-v5-hint/docs/pf-v5-hint.md @@ -0,0 +1,104 @@ +{% renderOverview %} + + Welcome to the new documentation experience. + Learn more about the improved features. + +{% endrenderOverview %} + +{% band header="Usage" %} + ### Default with no title + Basic hint without a title, used for simple informational messages. + + {% htmlexample %} + + Welcome to the new documentation experience. + Learn more about the improved features. + + {% endhtmlexample %} + + ### Hint with title + Add a title to your hint by using the `title` slot to provide more context. + + {% htmlexample %} + + Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. + + {% endhtmlexample %} + + ### With a footer + Add action links or buttons to the footer using the `footer` slot. + + {% htmlexample %} + + Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. + + Try it for 90 days + + + {% endhtmlexample %} + + ### With actions + Add an actions menu (like a kebab dropdown) using the `actions` slot. + + {% htmlexample %} + + + + + + Link + + + + + + + Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. + + {% endhtmlexample %} + + ### Complete example + A hint with all available slots. + + {% htmlexample %} + + + + + + Link + + + + + + Disabled link + + + + Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management to remediate all your systems across regions and geographies. + + Try it for 90 days + + + {% endhtmlexample %} + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderProperties %}{% endrenderProperties %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-hint/pf-v5-hint.css b/elements/pf-v5-hint/pf-v5-hint.css new file mode 100644 index 0000000000..2a5d34cf10 --- /dev/null +++ b/elements/pf-v5-hint/pf-v5-hint.css @@ -0,0 +1,120 @@ +:host { + display: block; + + /* Container */ + --pf-v5-c-hint--GridRowGap: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-hint--PaddingTop: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-hint--PaddingRight: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-hint--PaddingBottom: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-hint--PaddingLeft: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-hint--BackgroundColor: var(--pf-global--palette--blue-50, #e7f1fa); + --pf-v5-c-hint--BorderColor: var(--pf-global--palette--blue-100, #bee1f4); + --pf-v5-c-hint--BorderWidth: 1px; + --pf-v5-c-hint--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + --pf-v5-c-hint--BoxShadow: var(--pf-global--BoxShadow--sm, 0 0.0625rem 0.125rem 0 rgba(3, 3, 3, 0.12), 0 0 0.125rem 0 rgba(3, 3, 3, 0.06)); + + /* Title */ + --pf-v5-c-hint__title--FontSize: 1.125rem; + --pf-v5-c-hint__title--FontWeight: var(--pf-global--FontWeight--semi-bold, 700); + --pf-v5-c-hint__title--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-hint__title--LineHeight: var(--pf-global--LineHeight--md, 1.5); + + /* Body */ + --pf-v5-c-hint__body--FontSize: 1rem; + --pf-v5-c-hint__body--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-hint__body--LineHeight: var(--pf-global--LineHeight--md, 1.5); + + /* Footer */ + --pf-v5-c-hint__footer--MarginTop: 0; + --pf-v5-c-hint__footer--child--MarginRight: 1rem; + + /* Actions */ + --pf-v5-c-hint__actions--Top: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-hint__actions--Right: var(--pf-global--spacer--lg, 1.5rem); + --pf-v5-c-hint__actions--MarginLeft: 3rem; + --pf-v5-c-hint__actions--c-dropdown--MarginTop: calc(0.375rem * -1); +} + +#container { + position: relative; + display: grid; + grid-template-columns: 1fr auto; + gap: var(--pf-v5-c-hint--GridRowGap); + padding: + var(--pf-v5-c-hint--PaddingTop) + var(--pf-v5-c-hint--PaddingRight) + var(--pf-v5-c-hint--PaddingBottom) + var(--pf-v5-c-hint--PaddingLeft); + background-color: var(--pf-v5-c-hint--BackgroundColor); + border: var(--pf-v5-c-hint--BorderWidth) solid var(--pf-v5-c-hint--BorderColor); + border-radius: var(--pf-v5-c-hint--BorderRadius); + box-shadow: var(--pf-v5-c-hint--BoxShadow); + overflow: visible; +} + +#container > * { + grid-column: 1; +} + +#actions { + grid-column: 2; + grid-row: 1 / -1; + align-self: start; + position: relative; + z-index: 1000; +} + +#actions ::slotted(pf-v5-dropdown) { + margin-top: var(--pf-v5-c-hint__actions--c-dropdown--MarginTop); + --pf-v5-c-dropdown__menu--MinWidth: auto; + --pf-v5-c-dropdown__menu--Left: auto; + --pf-v5-c-dropdown__menu--Right: 0; +} + +#title { + font-size: var(--pf-v5-c-hint__title--FontSize); + font-weight: var(--pf-v5-c-hint__title--FontWeight); + color: var(--pf-v5-c-hint__title--Color); + line-height: var(--pf-v5-c-hint__title--LineHeight); +} + +#body { + font-size: var(--pf-v5-c-hint__body--FontSize); + color: var(--pf-v5-c-hint__body--Color); + line-height: var(--pf-v5-c-hint__body--LineHeight); +} + +#footer { + margin-top: var(--pf-v5-c-hint__footer--MarginTop); + font-size: var(--pf-v5-c-hint__body--FontSize); + line-height: var(--pf-v5-c-hint__body--LineHeight); +} + +#footer ::slotted(*) { + margin-right: var(--pf-v5-c-hint__footer--child--MarginRight); +} + +#footer ::slotted(*:last-child) { + margin-right: 0; +} + +/* Hidden elements */ +[hidden] { + display: none !important; +} + +/* Link styling within hint */ +::slotted(a) { + color: var(--pf-global--link--Color, #06c); + text-decoration: none; +} + +::slotted(a:hover) { + color: var(--pf-global--link--Color--hover, #004080); + text-decoration: underline; +} + +/* Button styling in footer */ +::slotted(pf-v5-button) { + --pf-v5-c-button--m-link--Color: var(--pf-global--link--Color, #06c); +} diff --git a/elements/pf-v5-hint/pf-v5-hint.ts b/elements/pf-v5-hint/pf-v5-hint.ts new file mode 100644 index 0000000000..c9a1348e0e --- /dev/null +++ b/elements/pf-v5-hint/pf-v5-hint.ts @@ -0,0 +1,68 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import styles from './pf-v5-hint.css'; + +/** + * A **hint** is in-app messaging that provides a one-step reminder, explanation, + * or call to action for a page or modal. Hints provide information about an interaction + * or prerequisite step that might not be immediately obvious to the user. + * + * @summary Provides inline contextual help or information to users + * @alias Hint + */ +@customElement('pf-v5-hint') +export class PfV5Hint extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + #slots = new SlotController(this, 'title', null, 'footer', 'actions'); + + render(): TemplateResult<1> { + const hasActions = !this.#slots.isEmpty('actions'); + + return html` +
+
+ + +
+
+ + +
+
+ + +
+ +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-hint': PfV5Hint; + } +} diff --git a/elements/pf-hint/test/pf-hint.e2e.ts b/elements/pf-v5-hint/test/pf-hint.e2e.ts similarity index 95% rename from elements/pf-hint/test/pf-hint.e2e.ts rename to elements/pf-v5-hint/test/pf-hint.e2e.ts index f6c748476f..2e258a693c 100644 --- a/elements/pf-hint/test/pf-hint.e2e.ts +++ b/elements/pf-v5-hint/test/pf-hint.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-hint'; +const tagName = 'pf-v5-hint'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-hint/test/pf-hint.spec.ts b/elements/pf-v5-hint/test/pf-hint.spec.ts new file mode 100644 index 0000000000..70673798b3 --- /dev/null +++ b/elements/pf-v5-hint/test/pf-hint.spec.ts @@ -0,0 +1,96 @@ +import { expect, html } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { PfV5Hint } from '@patternfly/elements/pf-v5-hint/pf-v5-hint.js'; +import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; + +describe('', function() { + describe('simply instantiating', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-hint')).to.be.an.instanceof(PfV5Hint); + }); + + it('should upgrade', async function() { + const el = await createFixture(html` + Basic hint + `); + const klass = customElements.get('pf-v5-hint'); + expect(el) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV5Hint); + }); + }); + + describe('basic hint', function() { + let element: PfV5Hint; + beforeEach(async function() { + element = await createFixture(html` + Welcome to the new documentation experience. + `); + }); + + it('should render body content, and not title footer, or actions', async function() { + const snap = await a11ySnapshot(); + expect(snap.children?.length).to.equal(1); + }); + }); + + describe('hint with title', function() { + let element: PfV5Hint; + beforeEach(async function() { + element = await createFixture(html` + + Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management. + + `); + await element.updateComplete; + }); + + it('should render title and body content', async function() { + const snap = await a11ySnapshot(); + expect(snap.children?.length).to.equal(2); + }); + }); + + describe('hint with footer', function() { + let element: PfV5Hint; + beforeEach(async function() { + element = await createFixture(html` + + Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management. + Try it for 90 days + + `); + await element.updateComplete; + }); + + it('should render footer', function() { + const footer = element.shadowRoot!.querySelector('#footer'); + expect(footer).to.exist; + }); + }); + + describe('hint with actions', function() { + let element: PfV5Hint; + beforeEach(async function() { + element = await createFixture(html` + + + Do more with Find it Fix it capabilities + Upgrade to Red Hat Smart Management. + + `); + await element.updateComplete; + }); + + it('should render title, body, and actions', async function() { + const { children: [actions, title, body, ...rest] = [] } = await a11ySnapshot(); + expect(actions.role).to.equal('button'); + expect(title.role).to.equal('text'); + expect(body.role).to.equal('text'); + expect(rest.length).to.equal(0); + }); + }); +}); diff --git a/elements/pf-v5-icon/README.md b/elements/pf-v5-icon/README.md new file mode 100644 index 0000000000..2afba8733d --- /dev/null +++ b/elements/pf-v5-icon/README.md @@ -0,0 +1,101 @@ +# PatternFly Elements Icon + +Read more about Icon in the [PatternFly Elements Icon documentation][docs] + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im/@patternfly/elements/), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; +``` + +## Usage + +Place the icon element on the page and give it an icon name from the [default icon set][icon-sets]. +In most cases, the icon should be labelled using `aria-label` or `aria-labelledby`, or removed from +the accessibility tree with `aria-hidden="true"` or `role="presentation"`, if its content is merely +presentational and expressed using accessible text copy elsewhere. + +```html + +``` + +### Fallback Content + +If you wish to display some content while the icon loads (or while JS is disabled), +you can slot it into ``. For instance, when using a checkmark icon in a +server status table, you may wish to display a checkmark emoji if JS is disabled. + +```html + +``` + +### Icon Sets + +Icon comes with three built-in icon sets: + +1. `fas`: Font Awesome Free Solid icons (the default set) +1. `far`: Font Awesome Free Regular icons +1. `fab`: Font Awesome Free Bold icons +1. `patternfly`: PatternFly icons + +Use the `set` attribute to pick an alternative icon set. +```html + + + +``` + +It is possible to add custom icon sets or override the default sets. +Icon sets are defined in detail in [the docs][icon-sets]. + +### Bundling and custom loading behaviour + +When bundling `` with other modules (e.g. using webpack, rollup, +esbuild, vite, or similar tools), icon imports will be code-split into chunks, +as they are imported from the `@patternfly/icons` package. Ensure that your +bundler is configured to permit dynamic imports, or mark the `@patternfly/icons` +package as "external" and apply an [import map][importmap] to your page instead. +If you would like to +customize the loading behaviour, override the `PfIcon.resolve()` static method. +This methods takes two arguments: the icon set (a string) and the icon name +(a string), and returns a promise of the icon contents, which is a DOM node, or +[anything else that lit-html can render][renderable]. + +```js +import { PfIcon } from '/pfe.min.js'; +PfIcon.resolve = async function(set, icon) { + try { + const { default: content } = await import(`/assets/icons/${set}/${icon}.js`); + if (content instanceof Node) { + return content.cloneNode(true); + } + } catch (e) { + return ''; + } +} +``` + +## Loading + +Icons load _lazily_ by default, meaning that the browser will not attempt to fetch and render the +icon until it scrolls into view. You can change this with the `loading` attribute; +see the [docs][docs] for more info. + +[docs]: https://patternflyelements.org/components/icon/ +[icon-sets]: https://patternflyelements.org/components/icon/#icon-sets +[renderable]: https://lit.dev/docs/components/rendering/#renderable-values +[importmap]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap diff --git a/elements/pf-v5-icon/demo/custom-icon-sets.html b/elements/pf-v5-icon/demo/custom-icon-sets.html new file mode 100644 index 0000000000..1a940f2eda --- /dev/null +++ b/elements/pf-v5-icon/demo/custom-icon-sets.html @@ -0,0 +1,38 @@ +
+

Custom Icon Sets

+

Call PfIcon.addIconSet with a string (icon set name) and a function taking the + icon set name and the icon name, and returning a URL (object) to a javascript module whose + default export is a lit renderable.

+ + + + + + + +
+ + + + diff --git a/elements/pf-icon/demo/icons/rh/boba-tea.js b/elements/pf-v5-icon/demo/icons/rh/boba-tea.js similarity index 100% rename from elements/pf-icon/demo/icons/rh/boba-tea.js rename to elements/pf-v5-icon/demo/icons/rh/boba-tea.js diff --git a/elements/pf-icon/demo/icons/rh/lifecycle.js b/elements/pf-v5-icon/demo/icons/rh/lifecycle.js similarity index 100% rename from elements/pf-icon/demo/icons/rh/lifecycle.js rename to elements/pf-v5-icon/demo/icons/rh/lifecycle.js diff --git a/elements/pf-v5-icon/demo/index.html b/elements/pf-v5-icon/demo/index.html new file mode 100644 index 0000000000..3318673ad3 --- /dev/null +++ b/elements/pf-v5-icon/demo/index.html @@ -0,0 +1,55 @@ +
+

Icon Sizes and Colours

+
+
Small
+
Medium
+
Large
+
X-Large
+
+
+
danger
+
default
+
info
+
success
+
warning
+
disabled
+
+
+ + + + + diff --git a/elements/pf-icon/demo/sets.html b/elements/pf-v5-icon/demo/sets.html similarity index 87% rename from elements/pf-icon/demo/sets.html rename to elements/pf-v5-icon/demo/sets.html index 2e20f2aefb..a86ea0fcfa 100644 --- a/elements/pf-icon/demo/sets.html +++ b/elements/pf-v5-icon/demo/sets.html @@ -8,12 +8,12 @@

Default Icon Sets

- + diff --git a/elements/pf-icon/docs/CHANGELOG.old.md b/elements/pf-v5-icon/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-icon/docs/CHANGELOG.old.md rename to elements/pf-v5-icon/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-icon/docs/pf-v5-icon.md b/elements/pf-v5-icon/docs/pf-v5-icon.md new file mode 100644 index 0000000000..3463cf47dc --- /dev/null +++ b/elements/pf-v5-icon/docs/pf-v5-icon.md @@ -0,0 +1,194 @@ +{% renderInstallation %} {% endrenderInstallation %} + + + +{% renderOverview %} + + + + +{% endrenderOverview %} + +{% band header="Usage" %} + Place the icon element on the page and give it an icon name from the [default + icon set][icon-sets]. In most cases, the icon should be labelled using + `aria-label` or `aria-labelledby`, or removed from the accessibility tree + with `aria-hidden="true"` or `role="presentation"`, if its content is merely + presentational and expressed using accessible text copy elsewhere. + + {% htmlexample %} + + + + + {% endhtmlexample %} + + ### Size + The default size is 1em, so icon size matches text size. `2x`, etc, are + multiples of font size. `sm`, `md`, etc are fixed pixel-based sizes. + + {% htmlexample %} + + + + + {% endhtmlexample %} +{% endband %} + +{% band header="Icon sets" %} +Icon comes with three built-in icon sets: + +1. `fas`: Font Awesome Free Solid icons (the default set) +1. `far`: Font Awesome Free Regular icons +1. `patternfly`: PatternFly icons + +Use the `set` attribute to pick an alternative icon set. +```html + + + +``` + +### Register a New Icon Set + +Icons are JavaScript module which export a [lit renderable][renderable], +typically an inline SVG element [template literal][template-literals] tagged +with the Lit [`svg`][svg-tag] template tag. To register a new icon set, call +the static `addIconSet` method with the set name and a getter function. The +getter function takes the icon set and icon name and returns a promise containing +the icon node, or any other [renderable][renderable] value. + +```ts +type IconResolveFunction = (set: string, icon: string) => Promise | Node; +``` + +```javascript +import { PfIcon } from '@patternfly/pf-v5-icon'; + +// Or, in a non-module context: +// const PfIcon = await customElements.whenDefined('pf-v5-icon'); + +PfIcon.addIconSet('local', (set, icon) => + import(`/assets/icons/${set}-${icon}.js`)) + .then(mod => mod.default); +``` + +### Updating an Existing Icon Set + +To updating an existing icon set, you use the same `addIconSet` function. By +defaulting back to then existing `getIconUrl` method, you you can add a new +icon to an existing set: + +```js +PfIcon.addIconSet('patternfly', (set, icon) => { + switch (icon) { + // add your custom icons + case 'my-custom-icon': + case 'other-custom-icon': + return import(`/icon-overrides/patternfly-custom/${icon}.js`) + .then(mod => mod.default); + // fall back to built-in icons + default: + return PfIcon.getIconUrl(set, icon); + } +}); +``` + +### Override the Default Icon Sets + +Icons are [loaded lazily](#loading) by default, so there's no performance +penalty for keeping the default icon sets around and unused. However, if you'd +like to override the default icon sets across the entire page, you can use +`addIconSet` with the `fas`, `far`, and `patternfly` set names: + +```js +import { PfIcon } from '@patternfly/pf-v5-icon'; + +PfIcon.resolve = (set, icon) => + import(`/icons/${set}-${icon}.js`)) + .then(mod => mod.default); +``` + +Now when `` is loaded from the [RedHat DX +CDN](https://redhatstatic.com/dx/), it will automatically load icon modules from +the CDN as well. + +{% endband %} + +{% band header="Loading" %} + +Icons load _lazily_ by default, meaning that the browser will not attempt to +fetch and render the icon until it scrolls into view. You can change this with +the `loading` attribute, which has three values: + +1. `lazy` (the default): load icons when they scroll into view +2. `idle`: load each icon on the page as soon as the browser enters an [idle + state][ric] Or, on less-capable browsers, at the next frame +3. `eager`: each icon will begin to load and render the moment it connects to + the DOM. + +You might choose to enable eager rendering for "above-the-fold" content, but +keep lazy loading for the rest of the page. + +```html +
+

+ + Hot News! +

+
+ +
+
...
+
+ +
+

+ + Contact Us +

+ ... +
+``` +{% endband %} + +{% renderSlots %} + +If you wish to display some content while the icon loads (or while JS is +disabled), you can slot it into ``. For instance, when using a +checkmark icon in a server status table, you may wish to display a checkmark +emoji if JS is disabled. + +```html + +``` + +It's recommended to use the icon name in the default slot, or +`aria-label(ledby)` so that the icon is accessible to screen readers. + +```html +check +``` +{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderProperties %}{% endrenderProperties %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} + +[renderable]: https://lit.dev/docs/components/rendering/ +[template-literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals +[svg-tag]: https://lit.dev/docs/api/templates/#svg +[ric]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback diff --git a/elements/pf-icon/docs/screenshot.png b/elements/pf-v5-icon/docs/screenshot.png similarity index 100% rename from elements/pf-icon/docs/screenshot.png rename to elements/pf-v5-icon/docs/screenshot.png diff --git a/elements/pf-v5-icon/pf-v5-icon.css b/elements/pf-v5-icon/pf-v5-icon.css new file mode 100644 index 0000000000..b749fd22f0 --- /dev/null +++ b/elements/pf-v5-icon/pf-v5-icon.css @@ -0,0 +1,35 @@ +:host { + position: relative; + display: inline-block; + line-height: 0; + height: fit-content !important; + width: fit-content !important; +} + +#container { + display: grid; + grid-template: 1fr / 1fr; + place-content: center; +} + +#container.content ::slotted(*) { + display: none; +} + +svg { + fill: currentcolor; +} + +:host([size=sm]) #container { --_size: var(--pf-global--icon--FontSize--sm, 10px); } +:host([size=md]) #container { --_size: var(--pf-global--icon--FontSize--md, 18px); } +:host([size=lg]) #container { --_size: var(--pf-global--icon--FontSize--lg, 24px); } +:host([size=xl]) #container { --_size: var(--pf-global--icon--FontSize--xl, 54px); } + +#container, svg { + /** size of the icon */ + width: var(--pf-v5-icon--size, var(--_size)); + height: var(--pf-v5-icon--size, var(--_size)); + min-width: var(--pf-v5-icon--size, var(--_size)); + min-height: var(--pf-v5-icon--size, var(--_size)); +} + diff --git a/elements/pf-v5-icon/pf-v5-icon.ts b/elements/pf-v5-icon/pf-v5-icon.ts new file mode 100644 index 0000000000..64434c8eb7 --- /dev/null +++ b/elements/pf-v5-icon/pf-v5-icon.ts @@ -0,0 +1,235 @@ +import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { state } from 'lit/decorators/state.js'; + +import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; + +import style from './pf-v5-icon.css'; + +type Renderable = unknown; + +export type IconResolverFunction = (set: string, icon: string) => + Renderable | Promise; + +/** + * requestIdleCallback when available, requestAnimationFrame when not + * @param f callback + */ +const ric: typeof globalThis.requestIdleCallback = + globalThis.requestIdleCallback + ?? globalThis.requestAnimationFrame + ?? (async (f: () => void) => Promise.resolve().then(f)); + +/** Fired when an icon fails to load */ +export class IconResolveError extends ErrorEvent { + constructor( + set: string, + icon: string, + /** The original error when importing the icon module */ + public originalError: Error + ) { + super('error', { message: `Could not load icon "${icon}" from set "${set}".` }); + } +} + +/** + * An **icon** component is a container that allows for icons of varying dimensions to + * seamlessly replace each other without shifting surrounding content. + * @alias Icon + * @fires load - Fired when an icon is loaded and rendered + * @fires error - Fired when an icon fails to load + */ +@customElement('pf-v5-icon') +export class PfV5Icon extends LitElement { + public static readonly styles: CSSStyleSheet[] = [style]; + + private static onIntersect: IntersectionObserverCallback = records => + records.forEach(({ isIntersecting, target }) => { + const icon = target as PfV5Icon; + icon.#intersecting = isIntersecting; + ric(() => { + if (icon.#intersecting) { + icon.#load(); + } + }); + }); + + private static defaultResolve: IconResolverFunction = (set: string, icon: string): Renderable => + import(`@patternfly/icons/${set}/${icon}.js`) + .then(mod => mod.default.cloneNode(true)); + + private static io = new IntersectionObserver(PfV5Icon.onIntersect); + + private static resolvers = new Map(); + + private static instances = new Set(); + + /** + * Register a new icon set + * @param setName - The name of the icon set + * @param resolver - A function that returns the URL of an icon + * @example returning a URL object + * ```js + * PfV5Icon.addIconSet('rh', (set, icon) => + * new URL(`./icons/${set}/${icon}.js`, import.meta.url)); + * ``` + * @example returning a string + * ```js + * PfV5Icon.addIconSet('rh', (set, icon) => + * `/assets/icons/${set}/${icon}.js`); + * ``` + */ + public static addIconSet(setName: string, resolver: IconResolverFunction): void { + if (typeof setName !== 'string') { + Logger.warn(`[${this.name}]: the first argument to addIconSet must be a string.`); + } else if (typeof resolver !== 'function') { + Logger.warn(`[${this.name}]: the second argument to addIconSet must be a function.`); + } else { + this.resolvers.set(setName, resolver); + for (const instance of this.instances) { + instance.#load(); + } + } + } + + /** Removes all added icon sets and resets resolve function */ + public static reset(): void { + this.resolvers.clear(); + this.resolve = this.defaultResolve; + } + + /** + * Gets a renderable icon. Override this to customize how icons are resolved. + * @param set - The name of the icon set + * @param icon - The name of the icon + * @returns The icon content, a node or anything else which lit-html can render + * @example resolving an icon node from an icon module + * ```js + * PfV5Icon.resolve = (set, icon) => + * import(`/assets/icons/${set}/${icon}.js`) + * .then(mod => mod.default.cloneNode(true)); + * ``` + * @example resolving a named export from an icon collection module + * ```js + * PfV5Icon.resolve = (set, icon) => + * import(`/assets/icons.js`) + * .then(module => module[icon]?.cloneNode(true)); + * ``` + * @example resolving a new node from an svg file + * ```js + * const iconCacne = new Map(); + * function getCachedIconOrNewNode(set, icon, svg) { + * const key = `${set}_${icon}`; + * if (!iconCache.has(key)) { + * const template = document.createElement('template'); + * template.innerHTML = svg; + * iconCache.set(key, template); + * } + * return iconCache.get(key); + * } + * PfV5Icon.resolve = (set, icon) => + * fetch(`/assets/icons/${set}/${icon}.svg`) + * .then(response => response.text()) + * .then(svg => getCachedIconOrNewNode(set, icon, svg)) + * .then(node => node.content.cloneNode(true)); + * ``` + */ + public static resolve: IconResolverFunction = PfV5Icon.defaultResolve; + + /** Icon set */ + @property() set = 'fas'; + + /** Icon name */ + @property({ reflect: true }) icon = ''; + + /** Size of the icon */ + @property({ reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'sm'; + + /** + * Controls how eager the element will be to load the icon data + * - `eager`: eagerly load the icon, blocking the main thread + * - `idle`: wait for the browser to attain an idle state before loading + * - `lazy` (default): wait for the element to enter the viewport before loading + */ + @property() loading?: 'idle' | 'lazy' | 'eager' = 'lazy'; + + /** Icon content. Any value that lit can render */ + @state() private content?: unknown; + + #intersecting = false; + + #logger = new Logger(this); + + #lazyLoad() { + PfV5Icon.io.observe(this); + if (this.#intersecting) { + this.load(); + } + } + + #load() { + switch (this.loading) { + case 'idle': return void ric(() => this.load()); + case 'lazy': return void this.#lazyLoad(); + case 'eager': return void this.load(); + } + } + + async #contentChanged() { + await this.updateComplete; + this.dispatchEvent?.(new Event('load', { bubbles: true })); + } + + connectedCallback(): void { + super.connectedCallback(); + PfV5Icon.instances.add(this); + } + + willUpdate(changed: PropertyValues): void { + if (changed.has('icon')) { + this.#load(); + } + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + PfV5Icon.io.unobserve(this); + PfV5Icon.instances.delete(this); + } + + render(): TemplateResult<1> { + const content = this.content ?? ''; + return html` + + `; + } + + protected async load(): Promise { + const { set, icon } = this; + const resolver = PfV5Icon.resolvers.get(set) ?? PfV5Icon.resolve; + if (set && icon && typeof resolver === 'function') { + try { + this.content = await resolver(set, icon); + this.#contentChanged(); + } catch (error: unknown) { + this.#logger.error((error as IconResolveError).message); + this.dispatchEvent?.(new IconResolveError(set, icon, error as Error)); + } + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-icon': PfV5Icon; + } +} diff --git a/elements/pf-icon/test/.gitignore b/elements/pf-v5-icon/test/.gitignore similarity index 100% rename from elements/pf-icon/test/.gitignore rename to elements/pf-v5-icon/test/.gitignore diff --git a/elements/pf-icon/test/pf-icon.e2e.ts b/elements/pf-v5-icon/test/pf-icon.e2e.ts similarity index 95% rename from elements/pf-icon/test/pf-icon.e2e.ts rename to elements/pf-v5-icon/test/pf-icon.e2e.ts index 54a8cca6c8..3b42e38234 100644 --- a/elements/pf-icon/test/pf-icon.e2e.ts +++ b/elements/pf-v5-icon/test/pf-icon.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-icon'; +const tagName = 'pf-v5-icon'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-icon/test/pf-icon.spec.ts b/elements/pf-v5-icon/test/pf-icon.spec.ts similarity index 77% rename from elements/pf-icon/test/pf-icon.spec.ts rename to elements/pf-v5-icon/test/pf-icon.spec.ts index 15ab3ae12c..02dd2d391b 100644 --- a/elements/pf-icon/test/pf-icon.spec.ts +++ b/elements/pf-v5-icon/test/pf-icon.spec.ts @@ -3,7 +3,7 @@ import { html, render, type TemplateResult } from 'lit'; import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; -import { PfIcon } from '@patternfly/elements/pf-icon/pf-icon.js'; +import { PfV5Icon } from '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; import '@patternfly/pfe-tools/test/stub-logger.js'; @@ -21,7 +21,7 @@ import bike from './rh-icon-bike.js'; const TEST_ICONS = { aed, api, atom, bike }; -async function expectIconsEqual(element: PfIcon, tpl: TemplateResult) { +async function expectIconsEqual(element: PfV5Icon, tpl: TemplateResult) { await oneEvent(element, 'load'); const rootNode = render(tpl, document.createDocumentFragment()); const actual = element.shadowRoot?.querySelector('svg'); @@ -29,52 +29,52 @@ async function expectIconsEqual(element: PfIcon, tpl: TemplateResult) { expect(actual?.outerHTML).to.equal(expected?.outerHTML); } -describe('', function() { - let element: PfIcon; +describe('', function() { + let element: PfV5Icon; beforeEach(async function() { - element = await fixture(html``); + element = await fixture(html``); }); afterEach(function() { - PfIcon.reset(); + PfV5Icon.reset(); }); it('imperatively instantiates', function() { - expect(document.createElement('pf-icon')).to.be.an.instanceof(PfIcon); + expect(document.createElement('pf-v5-icon')).to.be.an.instanceof(PfV5Icon); }); it('should upgrade', function() { - expect(element, 'pf-icon should be an instance of PfIcon') - .to.be.an.instanceOf(customElements.get('pf-icon')) + expect(element, 'pf-v5-icon should be an instance of PfV5Icon') + .to.be.an.instanceOf(customElements.get('pf-v5-icon')) .and - .to.be.an.instanceOf(PfIcon); + .to.be.an.instanceOf(PfV5Icon); }); describe('addIconSet', function() { describe('when 1st argument is not a string', function() { beforeEach(function() { - PfIcon.addIconSet( + PfV5Icon.addIconSet( // @ts-expect-error: testing bad input {}, () => void 0, ); }); it('should warn', function() { - expect(Logger.warn).to.have.been.calledOnceWith(`[PfIcon]: the first argument to addIconSet must be a string.`); + expect(Logger.warn).to.have.been.calledOnceWith(`[PfV5Icon]: the first argument to addIconSet must be a string.`); }); }); describe('when 2nd argument is not a function', function() { beforeEach(function() { - PfIcon.addIconSet( + PfV5Icon.addIconSet( 'rh', // @ts-expect-error: testing bad input 'haha' ); }); it('should warn', function() { - expect(Logger.warn).to.have.been.calledOnceWith(`[PfIcon]: the second argument to addIconSet must be a function.`); + expect(Logger.warn).to.have.been.calledOnceWith(`[PfV5Icon]: the second argument to addIconSet must be a function.`); }); }); @@ -83,7 +83,7 @@ describe('', function() { // replace the default built-in icon set resolveIconName function // with one that loads local icons. we don't want tests dependent on // prod servers. - PfIcon.addIconSet('rh', (_, icon: string) => TEST_ICONS[icon as keyof typeof TEST_ICONS]); + PfV5Icon.addIconSet('rh', (_, icon: string) => TEST_ICONS[icon as keyof typeof TEST_ICONS]); element.set = 'rh'; }); @@ -103,7 +103,7 @@ describe('', function() { .to.have.attribute('hidden'); }); - it('should change color when pf-icon\'s color CSS property is changed', async function() { + it('should change color when pf-v5-icon\'s color CSS property is changed', async function() { const newColor = 'rgb(11, 12, 13)'; element.style.setProperty('color', newColor); element.icon = 'atom'; @@ -115,7 +115,7 @@ describe('', function() { }); describe('changing size attribute', function() { - const sizes: PfIcon['size'][] = ['sm', 'md', 'lg', 'xl']; + const sizes: PfV5Icon['size'][] = ['sm', 'md', 'lg', 'xl']; let lastSize = { width: 0, height: 0 }; for (const size of sizes) { @@ -131,13 +131,13 @@ describe('', function() { }); describe('when the icon has a custom set attribute', function() { - let element: PfIcon; + let element: PfV5Icon; before(async function() { - element = await fixture(html``); + element = await fixture(html``); }); describe('then the icon set is registered', function() { beforeEach(async function() { - PfIcon.addIconSet('asdfasdf', () => + PfV5Icon.addIconSet('asdfasdf', () => import(`./rh-icon-${'bike'}.js`) .then(m => m.default)); await oneEvent(element, 'load'); @@ -149,12 +149,12 @@ describe('', function() { }); describe('when the icon has a fallback content', function() { - let element: PfIcon; + let element: PfV5Icon; before(async function() { element = await fixture(html` - +

Image failed to load.

. -
`); +
`); await oneEvent(element, 'error'); }); it('should display the fallback', function() { diff --git a/elements/pf-icon/test/rh-icon-aed.js b/elements/pf-v5-icon/test/rh-icon-aed.js similarity index 100% rename from elements/pf-icon/test/rh-icon-aed.js rename to elements/pf-v5-icon/test/rh-icon-aed.js diff --git a/elements/pf-icon/test/rh-icon-api.js b/elements/pf-v5-icon/test/rh-icon-api.js similarity index 100% rename from elements/pf-icon/test/rh-icon-api.js rename to elements/pf-v5-icon/test/rh-icon-api.js diff --git a/elements/pf-icon/test/rh-icon-atom.js b/elements/pf-v5-icon/test/rh-icon-atom.js similarity index 100% rename from elements/pf-icon/test/rh-icon-atom.js rename to elements/pf-v5-icon/test/rh-icon-atom.js diff --git a/elements/pf-icon/test/rh-icon-bike.js b/elements/pf-v5-icon/test/rh-icon-bike.js similarity index 100% rename from elements/pf-icon/test/rh-icon-bike.js rename to elements/pf-v5-icon/test/rh-icon-bike.js diff --git a/elements/pf-v5-jump-links/README.md b/elements/pf-v5-jump-links/README.md new file mode 100644 index 0000000000..386aa42edc --- /dev/null +++ b/elements/pf-v5-jump-links/README.md @@ -0,0 +1,27 @@ +# Jump Links + +**Jump links** allow users to navigate to sections within a page. + +Read more about Jump Links in the [documentation][docs]. + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-jump-links/pf-v5-jump-links.js'; +``` + +[docs]: https://patternflyelements.org/components/jump-links diff --git a/elements/pf-v5-jump-links/demo/centered-list.html b/elements/pf-v5-jump-links/demo/centered-list.html new file mode 100644 index 0000000000..eeb51f4c59 --- /dev/null +++ b/elements/pf-v5-jump-links/demo/centered-list.html @@ -0,0 +1,18 @@ +
+ + Inactive section + Active section + Inactive section + +
+ + + + diff --git a/elements/pf-v5-jump-links/demo/expandable-vertical-with-subsection.html b/elements/pf-v5-jump-links/demo/expandable-vertical-with-subsection.html new file mode 100644 index 0000000000..37f11644de --- /dev/null +++ b/elements/pf-v5-jump-links/demo/expandable-vertical-with-subsection.html @@ -0,0 +1,27 @@ +
+ + Inactive section + + Section with active subsection + + Active subsection + Inactive subsection + Inactive subsection + + + Inactive section + Inactive section + +
+ + + + diff --git a/elements/pf-v5-jump-links/demo/index.html b/elements/pf-v5-jump-links/demo/index.html new file mode 100644 index 0000000000..b3dd03adcd --- /dev/null +++ b/elements/pf-v5-jump-links/demo/index.html @@ -0,0 +1,18 @@ +
+ + Inactive section + Active section + Inactive section + +
+ + + + diff --git a/elements/pf-v5-jump-links/demo/label.html b/elements/pf-v5-jump-links/demo/label.html new file mode 100644 index 0000000000..d19a38cecd --- /dev/null +++ b/elements/pf-v5-jump-links/demo/label.html @@ -0,0 +1,24 @@ +
+ + Inactive section + Active section + Inactive section + + + Inactive section + Active section + Inactive section + +
+ + + + diff --git a/elements/pf-jump-links/demo/scrollspy-with-subsections.html b/elements/pf-v5-jump-links/demo/scrollspy-with-subsections.html similarity index 91% rename from elements/pf-jump-links/demo/scrollspy-with-subsections.html rename to elements/pf-v5-jump-links/demo/scrollspy-with-subsections.html index 49b3e60f4f..2d6ed3490d 100644 --- a/elements/pf-jump-links/demo/scrollspy-with-subsections.html +++ b/elements/pf-v5-jump-links/demo/scrollspy-with-subsections.html @@ -2,23 +2,23 @@

Main title

- +
@@ -61,7 +61,7 @@

Heading 2

recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.

- Button + Button

Heading 3

@@ -94,7 +94,7 @@

Heading 4

- Submit + Submit

At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum @@ -143,12 +143,12 @@

Heading 5

+ + diff --git a/elements/pf-v5-jump-links/demo/vertical.html b/elements/pf-v5-jump-links/demo/vertical.html new file mode 100644 index 0000000000..2df88f90c7 --- /dev/null +++ b/elements/pf-v5-jump-links/demo/vertical.html @@ -0,0 +1,18 @@ +
+ + Inactive section + Active section + Inactive section + +
+ + + + diff --git a/elements/pf-jump-links/docs/CHANGELOG.old.md b/elements/pf-v5-jump-links/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-jump-links/docs/CHANGELOG.old.md rename to elements/pf-v5-jump-links/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-jump-links/docs/pf-v5-jump-links.md b/elements/pf-v5-jump-links/docs/pf-v5-jump-links.md new file mode 100644 index 0000000000..91b33ae7e7 --- /dev/null +++ b/elements/pf-v5-jump-links/docs/pf-v5-jump-links.md @@ -0,0 +1,99 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} +
+

Basic

+ + Inactive section + Active section + Inactive section + +
+{% endrenderOverview %} + +{% band header="Usage" %} +
+

With centered list

+ {% htmlexample %} + + Inactive section + Active section + Inactive section + + {% endhtmlexample %} +
+ +
+

With label

+ {% htmlexample %} + + Inactive section + Active section + Inactive section + + {% endhtmlexample %} +
+ +
+

Vertical

+ {% htmlexample %} + + Inactive section + Active section + Inactive section + + {% endhtmlexample %} +
+ +
+

Expandable vertical with subsection

+ {% htmlexample %} + + Inactive section + + Section with active subsection + + Active subsection + Inactive subsection + Inactive subsection + + + Inactive section + Inactive section + + {% endhtmlexample %} +
+ +
+

Expandable vertical with Scrollspy

+ {% htmlexample %} + + Heading 1 + Heading 2 + Heading 3 + Heading 4 + Heading 5 + + {% endhtmlexample %} +
+{%endband %} + +{% renderSlots %}{% endrenderSlots %} +{% renderSlots for="pf-v5-jump-links-list", level=3, header="Slots on `pf-v5-jump-links-list`" %}{% endrenderSlots %} +{% renderSlots for="pf-v5-jump-links-item", level=3, header="Slots on `pf-v5-jump-links-item`" %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderProperties %}{% endrenderProperties %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-jump-links/docs/screenshot.png b/elements/pf-v5-jump-links/docs/screenshot.png similarity index 100% rename from elements/pf-jump-links/docs/screenshot.png rename to elements/pf-v5-jump-links/docs/screenshot.png diff --git a/elements/pf-v5-jump-links/pf-v5-jump-links-item.css b/elements/pf-v5-jump-links/pf-v5-jump-links-item.css new file mode 100644 index 0000000000..d2063f3f7e --- /dev/null +++ b/elements/pf-v5-jump-links/pf-v5-jump-links-item.css @@ -0,0 +1,75 @@ +:host { + display: block; +} + +#container { + display: contents; +} + +slot:not([name]) { + display: flex; + flex-direction: column; + row-gap: var(--pf-global--spacer--md, 1rem); +} + +a { + position: relative; + display: flex; + cursor: pointer; + flex: 1; + /** Top padding around the link */ + padding-block-start: var(--pf-v5-c-jump-links__link--PaddingTop, + var(--pf-global--spacer--md, 1rem)); + /** Right padding around the link */ + padding-inline-end: var(--pf-v5-c-jump-links__link--PaddingRight, + var(--pf-global--spacer--md, 1rem)); + /** Bottom padding around the link */ + padding-block-end: var(--pf-v5-c-jump-links__link--PaddingBottom, + var(--pf-global--spacer--md, 1rem)); + /** Left padding around the link */ + padding-inline-start: var(--pf-v5-c-jump-links__link--PaddingLeft, + var(--pf-global--spacer--md, 1rem)); + text-decoration: none; + /** Outline offset for jump links link */ + outline-offset: var(--pf-v5-c-jump-links__link--OutlineOffset, + calc(-1 * var(--pf-global--spacer--sm, 0.5rem))); + /** Color for jump links link text */ + color: var(--pf-v5-c-jump-links__link-text--Color, + var(--pf-global--Color--200, #6a6e73)); +} + +a::before { + position: absolute; + inset: 0; + pointer-events: none; + content: ""; + border-color: var(--pf-v5-c-jump-links__link--before--BorderColor, transparent); + border-style: solid; + border-width: + var(--pf-v5-c-jump-links__link--before--BorderTopWidth, + var(--pf-v5-c-jump-links__list--before--BorderTopWidth, + var(--pf-global--BorderWidth--sm, 1px))) + var(--pf-v5-c-jump-links__link--before--BorderRightWidth, 0) + var(--pf-v5-c-jump-links__link--before--BorderBottomWidth, 0) + var(--pf-v5-c-jump-links__link--before--BorderLeftWidth, 0); +} + +a:hover { + --pf-v5-c-jump-links__link-text--Color: var(--pf-v5-c-jump-links__link--hover__link-text--Color, + var(--pf-global--Color--100, #151515)); +} + +a:focus { + --pf-v5-c-jump-links__link-text--Color: var(--pf-v5-c-jump-links__link--focus__link-text--Color, + var(--pf-global--Color--100, #151515)); +} + +:host([active]) { + --pf-v5-c-jump-links__link--before--BorderTopWidth: var(--pf-v5-c-jump-links__item--m-current__link--before--BorderTopWidth, + var(--pf-global--BorderWidth--lg, 3px)); + --pf-v5-c-jump-links__link--before--BorderLeftWidth: var(--pf-v5-c-jump-links__item--m-current__link--before--BorderLeftWidth, 0); + --pf-v5-c-jump-links__link--before--BorderColor: var(--pf-v5-c-jump-links__item--m-current__link--before--BorderColor, + var(--pf-global--primary-color--100, #06c)); + --pf-v5-c-jump-links__link-text--Color: var(--pf-v5-c-jump-links__item--m-current__link-text--Color, + var(--pf-global--Color--100, #151515)); +} diff --git a/elements/pf-v5-jump-links/pf-v5-jump-links-item.ts b/elements/pf-v5-jump-links/pf-v5-jump-links-item.ts new file mode 100644 index 0000000000..2a0c862683 --- /dev/null +++ b/elements/pf-v5-jump-links/pf-v5-jump-links-item.ts @@ -0,0 +1,60 @@ +import { html, LitElement, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import { ifDefined } from 'lit/directives/if-defined.js'; + +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; + +import style from './pf-v5-jump-links-item.css'; + +import { observes } from '@patternfly/pfe-core/decorators/observes.js'; + +@customElement('pf-v5-jump-links-item') +export class PfV5JumpLinksItem extends LitElement { + static readonly styles: CSSStyleSheet[] = [style]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** Whether this item is active. */ + @property({ type: Boolean, reflect: true }) active = false; + + /** hypertext reference for this link */ + @property({ reflect: true }) href?: string; + + #internals = InternalsController.of(this, { role: 'listitem' }); + + render(): TemplateResult<1> { + return html` + + + + + `; + } + + @observes('active') + protected activeChanged(): void { + this.#internals.ariaCurrent = this.active ? 'location' : null; + } + + #onClick() { + this.dispatchEvent(new Event('select', { bubbles: true })); + } + + #onFocus() { + this.dispatchEvent(new Event('focus', { bubbles: true })); + } +} + + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-jump-links-item': PfV5JumpLinksItem; + } +} diff --git a/elements/pf-v5-jump-links/pf-v5-jump-links-list.css b/elements/pf-v5-jump-links/pf-v5-jump-links-list.css new file mode 100644 index 0000000000..4000176fc9 --- /dev/null +++ b/elements/pf-v5-jump-links/pf-v5-jump-links-list.css @@ -0,0 +1,18 @@ +:host { + display: block; +} + +#container { + display: contents; + --pf-v5-c-jump-links__list--PaddingTop: 0; + --pf-v5-c-jump-links__list--PaddingBottom: 0; + /** Top padding around each link */ + --pf-v5-c-jump-links__link--PaddingTop: var(--pf-v5-c-jump-links__list__list__link--PaddingTop, + var(--pf-global--spacer--sm, 0.5rem)); + /** Bottom padding around each link */ + --pf-v5-c-jump-links__link--PaddingBottom: var(--pf-v5-c-jump-links__list__list__link--PaddingBottom, + var(--pf-global--spacer--sm, 0.5rem)); + /** Left padding around each link */ + --pf-v5-c-jump-links__link--PaddingLeft: var(--pf-v5-c-jump-links__list__list__link--PaddingLeft, + var(--pf-global--spacer--lg, 1.5rem)); +} diff --git a/elements/pf-v5-jump-links/pf-v5-jump-links-list.ts b/elements/pf-v5-jump-links/pf-v5-jump-links-list.ts new file mode 100644 index 0000000000..e95d653ee3 --- /dev/null +++ b/elements/pf-v5-jump-links/pf-v5-jump-links-list.ts @@ -0,0 +1,22 @@ +import { html, LitElement, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; + +import style from './pf-v5-jump-links-list.css'; + +@customElement('pf-v5-jump-links-list') +export class PfV5JumpLinksList extends LitElement { + static readonly styles: CSSStyleSheet[] = [style]; + + render(): TemplateResult<1> { + // TODO: add label + // eslint-disable-next-line lit-a11y/accessible-name + return html`
`; + } +} + + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-jump-links-list': PfV5JumpLinksList; + } +} diff --git a/elements/pf-v5-jump-links/pf-v5-jump-links.css b/elements/pf-v5-jump-links/pf-v5-jump-links.css new file mode 100644 index 0000000000..27501fa401 --- /dev/null +++ b/elements/pf-v5-jump-links/pf-v5-jump-links.css @@ -0,0 +1,175 @@ +[hidden] { + display: none !important; +} + +:host { + display: block; +} + +#container { + display: flex; + flex-wrap: wrap; + font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); + font-size: var(--pf-global--FontSize--md, 16px); + font-weight: var(--pf-global--FontWeight--normal, 400); +} + +slot { + position: relative; + /** Controls the display type of the list container */ + display: var(--pf-v5-c-jump-links__list--Display, flex); + /** Controls the direction of items in the list */ + flex-direction: var(--pf-v5-c-jump-links__list--FlexDirection, row); + /** Padding around the list of links */ + padding-block-start: var(--pf-v5-c-jump-links__list--PaddingTop, 0); + /** Right padding for the list */ + padding-inline-end: var(--pf-v5-c-jump-links__list--PaddingRight, + var(--pf-global--spacer--md, 1rem)); + /** Bottom padding for the list */ + padding-block-end: var(--pf-v5-c-jump-links__list--PaddingBottom, 0); + /** Left padding for the list */ + padding-inline-start: var(--pf-v5-c-jump-links__list--PaddingLeft, + var(--pf-global--spacer--md, 1rem)); + /** Controls visibility of the list */ + visibility: var(--pf-v5-c-jump-links__list--Visibility, visible); +} + +slot::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + content: ""; + /** Border color for the list pseudo-element */ + border: solid var(--pf-v5-c-jump-links__list--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2)); + border-width: + /** Top border width for the list pseudo-element */ + var(--pf-v5-c-jump-links__list--before--BorderTopWidth, + var(--pf-global--BorderWidth--sm, 1px)) + /** Right border width for the list pseudo-element */ + var(--pf-v5-c-jump-links__list--before--BorderRightWidth, 0) + /** Bottom border width for the list pseudo-element */ + var(--pf-v5-c-jump-links__list--before--BorderBottomWidth, 0) + /** Left border width for the list pseudo-element */ + var(--pf-v5-c-jump-links__list--before--BorderLeftWidth, 0); +} + +:host([vertical]) #container { + /** Top padding for vertical layout */ + --pf-v5-c-jump-links__list--PaddingTop: + var(--pf-v5-c-jump-links--m-vertical__list--PaddingTop, + var(--pf-global--spacer--md, 1rem) + ); + /** Right padding for vertical layout */ + --pf-v5-c-jump-links__list--PaddingRight: + var(--pf-v5-c-jump-links--m-vertical__list--PaddingRight,0); + /** Bottom padding for vertical layout */ + --pf-v5-c-jump-links__list--PaddingBottom: + var(--pf-v5-c-jump-links--m-vertical__list--PaddingBottom, + var(--pf-global--spacer--md, 1rem) + ); + /** Left padding for vertical layout */ + --pf-v5-c-jump-links__list--PaddingLeft: + var(--pf-v5-c-jump-links--m-vertical__list--PaddingLeft, 0); + /** Top border width for vertical layout */ + --pf-v5-c-jump-links__list--before--BorderTopWidth: + var(--pf-v5-c-jump-links--m-vertical__list--before--BorderTopWidth, 0); + /** Left border width for vertical layout */ + --pf-v5-c-jump-links__list--before--BorderLeftWidth: + var(--pf-v5-c-jump-links--m-vertical__list--before--BorderLeftWidth, + var(--pf-global--BorderWidth--sm, 1px)); + /** Top border width for current item in vertical layout */ + --pf-v5-c-jump-links__item--m-current__link--before--BorderTopWidth: + var(--pf-v5-c-jump-links--m-vertical__item--m-current__link--before--BorderTopWidth, 0); + /** Left border width for current item in vertical layout */ + --pf-v5-c-jump-links__item--m-current__link--before--BorderLeftWidth: + var(--pf-v5-c-jump-links--m-vertical__item--m-current__link--before--BorderLeftWidth, + var(--pf-global--BorderWidth--lg, 3px) + ); + /** Flex direction for vertical layout */ + --pf-v5-c-jump-links__list--FlexDirection: var(--pf-v5-c-jump-links--m-vertical__list--FlexDirection, column); +} + +:host([centered]) #container { + justify-content: center; +} + +:host([centered]) #label { + text-align: center; +} + +:host(:not([expandable])) #label { + display: block; + width: 100%; + margin-block-end: var(--pf-v5-c-jump-links__label--MarginBottom, + var(--pf-global--spacer--md, 1rem)); +} + +:host([expandable]) { + /** Base margin for the toggle button */ + --pf-v5-c-jump-links--m-expanded__toggle--MarginBottom: + calc(var(--pf-v5-c-jump-links__toggle--MarginBottom--base, + calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)) + ) + var(--pf-global--spacer--md, 1rem)); +} + +summary { + display: block; + /** Padding around the expandable jump links disclosure widget. */ + margin-block-start: var(--pf-v5-c-jump-links__toggle--MarginTop, + calc(-1 * var(--pf-global--spacer--form-element, 0.375rem))); + /** Bottom margin for the toggle */ + margin-block-end: var(--pf-v5-c-jump-links__toggle--MarginBottom, + var(--pf-v5-c-jump-links__toggle--MarginBottom--base, + calc(-1 * var(--pf-global--spacer--form-element, 0.375rem)))); + /** Left margin for the toggle */ + margin-inline-start: var(--pf-v5-c-jump-links__toggle--MarginLeft); + /** Color of the toggle text */ + color: var(--pf-v5-c-jump-links__toggle-text--Color, + var(--pf-global--Color--100, #151515)); + /** Padding around the expandable jump links disclosure widget. */ + padding: + var(--pf-v5-c-button--PaddingTop, + var(--pf-global--spacer--form-element, 0.375rem)) + /** Right padding for the button */ + var(--pf-v5-c-button--PaddingRight, + var(--pf-global--spacer--md, 1rem)) + /** Bottom padding for the button */ + var(--pf-v5-c-button--PaddingBottom, + var(--pf-global--spacer--form-element, 0.375rem)) + /** Left padding for the button */ + var(--pf-v5-c-button--PaddingLeft, + var(--pf-global--spacer--md, 1rem)); +} + +summary pf-v5-icon { + /** Rotation angle for the toggle icon */ + rotate: var(--pf-v5-c-jump-links__toggle-icon--Rotate, 0); + /** Transition for the toggle icon */ + transition: var(--pf-v5-c-jump-links__toggle-icon--Transition, + var(--pf-global--Transition, all 250ms cubic-bezier(0.42, 0, 0.58, 1))); +} + +summary span { + /** Left margin for the toggle text */ + margin-inline-start: var(--pf-v5-c-jump-links__toggle-text--MarginLeft, + var(--pf-global--spacer--md, 1rem)); +} + +:host([expanded]) #container { + /** Bottom margin for expanded toggle */ + --pf-v5-c-jump-links__toggle--MarginBottom: var(--pf-v5-c-jump-links--m-expanded__toggle--MarginBottom, + calc(var(--pf-v5-c-jump-links__toggle--MarginBottom--base, + calc(-1 * var(--pf-global--spacer--form-element, 0.375rem))) + + var(--pf-global--spacer--md, 1rem))); + /** Rotation for expanded toggle icon */ + --pf-v5-c-jump-links__toggle-icon--Rotate: var(--pf-v5-c-jump-links--m-expanded__toggle-icon--Rotate, + 90deg); + /** Color for expanded toggle icon */ + --pf-v5-c-jump-links__toggle-icon--Color: var(--pf-v5-c-jump-links--m-expanded__toggle-icon--Color, + var(--pf-global--Color--100, #151515)); + --pf-v5-c-jump-links__toggle-icon--Rotate: 90deg; +} diff --git a/elements/pf-v5-jump-links/pf-v5-jump-links.ts b/elements/pf-v5-jump-links/pf-v5-jump-links.ts new file mode 100644 index 0000000000..8a87910860 --- /dev/null +++ b/elements/pf-v5-jump-links/pf-v5-jump-links.ts @@ -0,0 +1,138 @@ +import { html, LitElement, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import { ScrollSpyController } from '@patternfly/pfe-core/controllers/scroll-spy-controller.js'; +import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; + +import { PfV5JumpLinksItem } from './pf-v5-jump-links-item.js'; + +import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; + +import style from './pf-v5-jump-links.css'; + +/** + * **Jump links** allow users to navigate to sections within a page. + * @alias Jump Links + * @fires toggle - when the `expanded` disclosure widget is toggled + */ +@customElement('pf-v5-jump-links') +export class PfV5JumpLinks extends LitElement { + static readonly styles: CSSStyleSheet[] = [style]; + + /** Whether the element features a disclosure widget around the nav items */ + @property({ reflect: true, type: Boolean }) expandable = false; + + /** Whether the expandable element's disclosure widget is expanded */ + @property({ reflect: true, type: Boolean }) expanded = false; + + /** Whether the layout of children is vertical or horizontal. */ + @property({ reflect: true, type: Boolean }) vertical = false; + + /** Whether to center children. */ + @property({ reflect: true, type: Boolean }) centered = false; + + /** Offset to add to the scroll position, potentially for a masthead which content scrolls under. */ + @property({ type: Number }) offset = 0; + + /** Label to add to nav element. Required for accessibility. */ + @property({ reflect: true }) label = 'Jump to section'; + + #kids = this.querySelectorAll?.(':is(pf-v5-jump-links-item, pf-v5-jump-links-list)'); + + get #items() { + return Array.from(this.#kids ?? []) + .flatMap(i => [ + ...i.shadowRoot?.querySelectorAll?.('a') ?? [], + ...i.querySelectorAll?.('a') ?? [], + ]); + } + + #tabindex = RovingTabindexController.of(this, { + getItems: () => this.#items, + }); + + #spy = new ScrollSpyController(this, { + rootMargin: `${this.offset}px 0px 0px 0px`, + tagNames: ['pf-v5-jump-links-item'], + }); + + protected override async getUpdateComplete(): Promise { + const here = await super.getUpdateComplete(); + const ps = await Promise.all(Array.from(this.#kids, x => x.updateComplete)); + return here && ps.every(x => !!x); + } + + override connectedCallback(): void { + super.connectedCallback(); + this.addEventListener('slotchange', this.#onSlotChange); + this.addEventListener('select', this.#onSelect); + } + + override firstUpdated(): void { + const active = this.querySelector?.('pf-v5-jump-links-item[active]'); + if (active) { + this.#setActiveItem(active); + } + } + + override updated(changed: Map): void { + if (changed.has('offset')) { + this.#spy.rootMargin = `${this.offset ?? 0}px 0px 0px 0px`; + } + } + + render(): TemplateResult<1> { + return html` + + `; + } + + #onSlotChange() { + this.#tabindex.items = this.#items; + } + + #onSelect(event: Event) { + if (event.target instanceof PfV5JumpLinksItem) { + this.#setActiveItem(event.target); + } + } + + #setActiveItem(item: PfV5JumpLinksItem) { + const itemLink = item.shadowRoot?.querySelector?.('a') ?? null; + if (itemLink) { + this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(itemLink); + this.#spy.setActive(item); + } + } + + #onToggle(event: Event) { + if (event.target instanceof HTMLDetailsElement) { + this.expanded = event.target.open; + } + this.dispatchEvent(new Event('toggle')); + } +} + + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-jump-links': PfV5JumpLinks; + } +} diff --git a/elements/pf-jump-links/test/pf-jump-links.e2e.ts b/elements/pf-v5-jump-links/test/pf-jump-links.e2e.ts similarity index 86% rename from elements/pf-jump-links/test/pf-jump-links.e2e.ts rename to elements/pf-v5-jump-links/test/pf-jump-links.e2e.ts index 739c64c4e8..44b535b9c0 100644 --- a/elements/pf-jump-links/test/pf-jump-links.e2e.ts +++ b/elements/pf-v5-jump-links/test/pf-jump-links.e2e.ts @@ -2,12 +2,12 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-jump-links'; +const tagName = 'pf-v5-jump-links'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { const componentPage = new PfeDemoPage(page, tagName); - await componentPage.navigate({ selector: 'pf-jump-links-nav' }); + await componentPage.navigate({ selector: 'pf-v5-jump-links-nav' }); await componentPage.snapshot(); }); diff --git a/elements/pf-v5-jump-links/test/pf-jump-links.spec.ts b/elements/pf-v5-jump-links/test/pf-jump-links.spec.ts new file mode 100644 index 0000000000..9b67c533ae --- /dev/null +++ b/elements/pf-v5-jump-links/test/pf-jump-links.spec.ts @@ -0,0 +1,98 @@ +import { expect, html, nextFrame } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { sendKeys } from '@web/test-runner-commands'; + +import { PfV5JumpLinks } from '../pf-v5-jump-links.js'; +import { PfV5JumpLinksItem } from '../pf-v5-jump-links-item.js'; +import { PfV5JumpLinksList } from '../pf-v5-jump-links-list.js'; + +import '@patternfly/pfe-tools/test/stub-logger.js'; +import { allUpdates } from '@patternfly/pfe-tools/test/utils.js'; + +describe('', function() { + let element: PfV5JumpLinks; + let firstItem: PfV5JumpLinksItem; + let secondItem: PfV5JumpLinksItem; + + beforeEach(async function() { + element = await createFixture(html` + + Inactive section + Active section + Inactive section + + `); + await allUpdates(element); + [firstItem, secondItem] = element.querySelectorAll('pf-v5-jump-links-item'); + }); + + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-jump-links')).to.be.an.instanceof(PfV5JumpLinks); + expect(document.createElement('pf-v5-jump-links-item')).to.be.an.instanceof(PfV5JumpLinksItem); + expect(document.createElement('pf-v5-jump-links-list')).to.be.an.instanceof(PfV5JumpLinksList); + }); + + describe('tabbing to first item', function() { + let initialActiveElement: Element | null; + beforeEach(async function() { + initialActiveElement = document.activeElement; + await sendKeys({ press: 'Tab' }); + await nextFrame(); + }); + + it('should focus the first jump-links-item', function() { + expect(document.activeElement).to.equal(firstItem); + }); + + describe('pressing right arrow key', function() { + beforeEach(async function() { + await sendKeys({ press: 'ArrowRight' }); + await allUpdates(element); + await nextFrame(); + }); + it('should focus a jump-links-item', function() { + expect(document.activeElement).to.be.an.instanceof(PfV5JumpLinksItem); + }); + it('should change focus when keyboard navigation is used', function() { + expect(document.activeElement).to.not.equal(initialActiveElement); + }); + it('should focus the second jump links item', function() { + expect(document.activeElement).to.equal(secondItem); + }); + }); + }); +}); + +describe('', function() { + let element: PfV5JumpLinksItem; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should upgrade', async function() { + expect(element) + .to.be.an.instanceof(customElements.get('pf-v5-jump-links-item')) + .and + .to.be.an.instanceof(PfV5JumpLinksItem); + }); +}); + +describe('', function() { + let element: PfV5JumpLinksList; + + beforeEach(async function() { + element = await createFixture(html` + + `); + }); + + it('should upgrade', async function() { + expect(element) + .to.be.an.instanceof(customElements.get('pf-v5-jump-links-list')) + .and + .to.be.an.instanceof(PfV5JumpLinksList); + }); +}); diff --git a/elements/pf-v5-label-group/README.md b/elements/pf-v5-label-group/README.md new file mode 100644 index 0000000000..916f75e55d --- /dev/null +++ b/elements/pf-v5-label-group/README.md @@ -0,0 +1,46 @@ +# Label Group +A label group is a collection of labels that can be grouped by category and +used to represent one or more values assigned to a single attribute. When +the number of labels exceeds the configured limit, additional labels are +hidden under an overflow indicator. + +Read more about Label Group in the [PatternFly Elements Label Group documentation](https://patternflyelements.org/components/label-group) + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-label-group/pf-v5-label-group.js'; +``` + +## Usage + +```html + + Security + Performance + Networking + +``` + +With a category: +```html + + Filters + Security + Performance + +``` diff --git a/elements/pf-v5-label-group/demo/index.html b/elements/pf-v5-label-group/demo/index.html new file mode 100644 index 0000000000..f77c341f9d --- /dev/null +++ b/elements/pf-v5-label-group/demo/index.html @@ -0,0 +1,9 @@ + + Blue + Green + Orange + + + diff --git a/elements/pf-v5-label-group/demo/label-group-closeable.html b/elements/pf-v5-label-group/demo/label-group-closeable.html new file mode 100644 index 0000000000..cd7da14974 --- /dev/null +++ b/elements/pf-v5-label-group/demo/label-group-closeable.html @@ -0,0 +1,10 @@ + + Filters + Security + Performance + Networking + + + diff --git a/elements/pf-v5-label-group/demo/label-group-vertical.html b/elements/pf-v5-label-group/demo/label-group-vertical.html new file mode 100644 index 0000000000..4633541e7e --- /dev/null +++ b/elements/pf-v5-label-group/demo/label-group-vertical.html @@ -0,0 +1,10 @@ + + Group + Label 1 + Label 2 + Label 3 + + + diff --git a/elements/pf-v5-label-group/demo/label-group-with-a-very-long-name.html b/elements/pf-v5-label-group/demo/label-group-with-a-very-long-name.html new file mode 100644 index 0000000000..3a6e7d131b --- /dev/null +++ b/elements/pf-v5-label-group/demo/label-group-with-a-very-long-name.html @@ -0,0 +1,10 @@ + + Label group with a very long category name + Label 1 + Label 2 + Label 3 + + + diff --git a/elements/pf-v5-label-group/demo/label-group-with-overflow-labels.html b/elements/pf-v5-label-group/demo/label-group-with-overflow-labels.html new file mode 100644 index 0000000000..c50d98afb1 --- /dev/null +++ b/elements/pf-v5-label-group/demo/label-group-with-overflow-labels.html @@ -0,0 +1,13 @@ + + Tags + Label 1 + Label 2 + Label 3 + Label 4 + Label 5 + Label 6 + + + diff --git a/elements/pf-v5-label-group/docs/pf-v5-label-group.md b/elements/pf-v5-label-group/docs/pf-v5-label-group.md new file mode 100644 index 0000000000..8a867f5b03 --- /dev/null +++ b/elements/pf-v5-label-group/docs/pf-v5-label-group.md @@ -0,0 +1,17 @@ +{% renderOverview %} + +{% endrenderOverview %} + +{% band header="Usage" %}{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-label-group/pf-v5-label-group.css b/elements/pf-v5-label-group/pf-v5-label-group.css new file mode 100644 index 0000000000..eee2a9e953 --- /dev/null +++ b/elements/pf-v5-label-group/pf-v5-label-group.css @@ -0,0 +1,100 @@ +:host { + --pf-v5-c-label-group__list--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + --pf-v5-c-label-group__list--MarginRight: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + --pf-v5-c-label-group--m-category--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-label-group--m-category--PaddingRight: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-label-group--m-category--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-label-group--m-category--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-label-group--m-category--BorderRadius: var(--pf-global--BorderRadius--sm, 3px); + --pf-v5-c-label-group--m-category--BackgroundColor: var(--pf-global--BackgroundColor--200, #f0f0f0); + --pf-v5-c-label-group__label--MarginRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-label-group__label--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + --pf-v5-c-label-group__label--MaxWidth: 18ch; + --pf-v5-c-label-group__close--MarginTop: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + --pf-v5-c-label-group__close--MarginBottom: calc(var(--pf-global--spacer--xs, 0.25rem) * -1); + --pf-v5-c-label-group__list-item--MarginRight: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-label-group__list-item--MarginBottom: var(--pf-global--spacer--xs, 0.25rem); + display: inline-flex; + flex-wrap: wrap; + align-items: center; + min-width: 0; + max-width: 100%; + color: var(--pf-global--Color--100, #151515); +} + +[hidden], +.empty, +::slotted([hidden]), +::slotted([overflow-hidden]) { + display: none !important; +} + +#outer { + display: inline-flex; + flex: 1; + flex-wrap: wrap; + align-items: center; + min-width: 0; + column-gap: var(--pf-global--spacer--xs, 0.25rem); + border-radius: var(--pf-global--BorderRadius--sm, 3px); + padding: 0 !important; +} + +#outer.has-category { + padding: + var(--pf-v5-c-label-group--m-category--PaddingTop) + var(--pf-v5-c-label-group--m-category--PaddingRight) + var(--pf-v5-c-label-group--m-category--PaddingBottom) + var(--pf-v5-c-label-group--m-category--PaddingLeft) !important; + background-color: var(--pf-v5-c-label-group--m-category--BackgroundColor); +} + +#labels { + margin-inline-end: var(--pf-v5-c-label-group__list--MarginRight); + margin-block-end: var(--pf-v5-c-label-group__list--MarginBottom); + font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); + font-size: var(--pf-global--FontSize--sm, 14px); + font-weight: var(--pf-global--FontWeight--normal, 400); + line-height: 1.6; +} + +#labels ::slotted(pf-v5-label) { + display: inline-flex; + min-width: 0; + margin-inline-end: var(--pf-v5-c-label-group__list-item--MarginRight); + margin-block-end: var(--pf-v5-c-label-group__list-item--MarginBottom); +} + +::slotted([slot="category"]) { + display: inline-block; + max-inline-size: var(--pf-v5-c-label-group__label--MaxWidth); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; + margin-inline-end: var(--pf-v5-c-label-group__label--MarginRight); + font-size: var(--pf-v5-c-label-group__label--FontSize); +} + +:host([orientation="vertical"]) #outer { + flex-direction: column; + align-items: flex-start; +} + +#close-button { + --pf-v5-icon--size: 16px; + margin-block: var(--pf-v5-c-label-group__close--MarginTop) var(--pf-v5-c-label-group__close--MarginBottom); + inset-block-start: 0.125em; +} + +.visually-hidden { + border: 0; + clip: rect(0, 0, 0, 0); + block-size: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + inline-size: 1px; +} diff --git a/elements/pf-v5-label-group/pf-v5-label-group.ts b/elements/pf-v5-label-group/pf-v5-label-group.ts new file mode 100644 index 0000000000..64320a9452 --- /dev/null +++ b/elements/pf-v5-label-group/pf-v5-label-group.ts @@ -0,0 +1,221 @@ +import { LitElement, html, isServer, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { observes } from '@patternfly/pfe-core/decorators/observes.js'; +import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js'; + +import { PfV5Label } from '../pf-v5-label/pf-v5-label.js'; + +import styles from './pf-v5-label-group.css'; + +export class PfV5LabelGroupExpandEvent extends Event { + constructor() { + super('expand', { bubbles: true, cancelable: true }); + } +} + +export class PfV5LabelGroupRemoveEvent extends Event { + constructor() { + super('remove', { bubbles: true, cancelable: true }); + } +} + +/** + * `${` + * **WS** (_>= 0x_) + * `remaining` + * **WS** (_>= 0x_) + * `}` + */ +const REMAINING_RE = /\$\{\s*remaining\s*\}/g; + +/** + * A **label group** is a collection of labels that can be grouped by category + * and used to represent one or more values assigned to a single attribute. + * When the number of labels exceeds `numLabels`, additional labels will be + * hidden using an overflow label. + * + * @alias Label Group + * @summary Groups multiple labels with overflow, category, and close support. + * + * @fires {PfV5LabelGroupExpandEvent} expand - Fires when label group is expanded to show all labels + * @fires {PfV5LabelGroupRemoveEvent} remove - Fires when label group is closed/removed + * + * @slot category + * Category name text for label group category. + * If this slot is populated, the label group will have category styling applied. + * @slot - `` elements. + */ +@customElement('pf-v5-label-group') +export class PfV5LabelGroup extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** Orientation of the label group. */ + @property({ reflect: true }) orientation: 'horizontal' | 'vertical' = 'horizontal'; + + /** Accessible label for the label group when no category name is provided. */ + @property({ attribute: 'accessible-label' }) accessibleLabel = ''; + + /** Accessible label for the close button. */ + @property({ attribute: 'accessible-close-label' }) accessibleCloseLabel = 'Close'; + + /** + * Customizable "more" template string. + * Use variable `${remaining}` for the overflow label count. + */ + @property({ attribute: 'collapsed-text' }) collapsedText = '${remaining} more'; + + /** Customizable "show less" text string. */ + @property({ attribute: 'expanded-text' }) expandedText = 'show less'; + + /** Number of labels to show before overflow. */ + @property({ attribute: 'num-labels', type: Number }) numLabels = 3; + + /** Whether overflow labels are visible. */ + @property({ reflect: true, type: Boolean }) open = false; + + /** Whether the label group can be closed. */ + @property({ reflect: true, type: Boolean }) closeable = false; + + /** Label count tracked during SSR via child events. */ + _ssrLabelCount = 0; + + get #overflowLabel(): PfV5Label | null { + return this.renderRoot?.querySelector?.('#overflow') ?? null; + } + + get #closeButton(): HTMLButtonElement | null { + return this.renderRoot?.querySelector?.('#close-button') ?? null; + } + + get #categorySlotted(): Node[] { + const slot = this.renderRoot + ?.querySelector?.('slot[name="category"]'); + return slot?.assignedNodes({ flatten: true }) ?? []; + } + + get #labels(): NodeListOf | PfV5Label[] { + if (isServer) { + return [] as PfV5Label[]; + } + return this.querySelectorAll('pf-v5-label:not([slot]):not([overflow-label])'); + } + + get #labelCount(): number { + if (isServer) { + return this._ssrLabelCount; + } + return this.#labels.length; + } + + get #hasCategory(): boolean { + return this.#categorySlotted.length > 0; + } + + get #remaining(): number { + return this.#labelCount - this.numLabels; + } + + #tabindex = RovingTabindexController.of(this, { + getItems: () => [ + ...Array.prototype.slice.call( + this.#labels, + 0, + this.open ? this.#labels.length : Math.min(this.#labels.length, this.numLabels), + ), + this.#overflowLabel, + this.#closeButton, + ].filter(x => !!x), + }); + + constructor() { + super(); + this.addEventListener('remove', this.#onRemove); + this.addEventListener('ssr:label', () => this._ssrLabelCount++); + } + + override render(): TemplateResult<1> { + const empty = this.#labelCount <= 0; + return html` + + `; + } + + /** Updates labels when relevant properties change. */ + @observes('numLabels') + @observes('closeable') + @observes('open') + private labelsChanged(): void { + this.#updateOverflow(); + } + + #onCloseClick() { + this.dispatchEvent(new PfV5LabelGroupRemoveEvent()); + } + + async #onMoreClick(event: Event) { + event.stopPropagation(); + this.open = !this.open; + await this.updateComplete; + this.labelsChanged(); + if (this.#overflowLabel) { + this.#tabindex.atFocusedItemIndex = this.#tabindex.items.indexOf(this.#overflowLabel); + } + this.dispatchEvent(new PfV5LabelGroupExpandEvent()); + } + + #onSlotchange() { + this.requestUpdate(); + } + + #onRemove(event: Event) { + if (event instanceof PfV5LabelGroupRemoveEvent) { + this.remove(); + } + } + + #updateOverflow() { + this.#labels.forEach((label, i) => { + label.hidden = i >= this.numLabels && !this.open; + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-label-group': PfV5LabelGroup; + } +} diff --git a/elements/pf-label-group/test/pf-label-group.e2e.ts b/elements/pf-v5-label-group/test/pf-label-group.e2e.ts similarity index 95% rename from elements/pf-label-group/test/pf-label-group.e2e.ts rename to elements/pf-v5-label-group/test/pf-label-group.e2e.ts index 790889b1d6..4f70a1ebeb 100644 --- a/elements/pf-label-group/test/pf-label-group.e2e.ts +++ b/elements/pf-v5-label-group/test/pf-label-group.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-label-group'; +const tagName = 'pf-v5-label-group'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-label-group/test/pf-label-group.spec.ts b/elements/pf-v5-label-group/test/pf-label-group.spec.ts new file mode 100644 index 0000000000..a2578da959 --- /dev/null +++ b/elements/pf-v5-label-group/test/pf-label-group.spec.ts @@ -0,0 +1,173 @@ +import { expect, html } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { a11ySnapshot, querySnapshotAll } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; +import { PfV5LabelGroup } from '../pf-v5-label-group.js'; +import { sendKeys } from '@web/test-runner-commands'; + +function press(key: string) { + return async function() { + await sendKeys({ press: key }); + }; +} + +describe('', function() { + let element: PfV5LabelGroup; + + describe('simply instantiating', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-label-group')).to.be.an.instanceof(PfV5LabelGroup); + }); + + it('should upgrade', async function() { + element = await createFixture(html``); + const klass = customElements.get('pf-v5-label-group'); + expect(element) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV5LabelGroup); + }); + }); + + describe('with 4 labels', function() { + beforeEach(async function() { + element = await createFixture(html` + + Label 1 + Label 2 + Label 3 + Label 4 + + `); + }); + + it('displays 3 labels and an overflow button', async function() { + const snapshot = await a11ySnapshot(); + expect(querySnapshotAll(snapshot, { name: /^Label/ })).to.have.length(3); + }); + + describe('clicking overflow label', function() { + beforeEach(async function() { + const overflow = element.shadowRoot?.querySelector('#overflow') as HTMLElement; + overflow?.click(); + await element.updateComplete; + }); + it('should show all labels', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(4); + }); + it('should show collapse text', async function() { + const snapshot = await a11ySnapshot(); + expect(querySnapshotAll(snapshot, { name: 'show less' })).to.have.length(1); + }); + }); + }); + + describe('with 4 labels and `closeable` attribute', function() { + const updateComplete = () => element.updateComplete; + + beforeEach(async function() { + element = await createFixture(html` + + Label 1 + Label 2 + Label 3 + Label 4 + + `); + }); + + beforeEach(updateComplete); + + it('should have a close button', async function() { + const snapshot = await a11ySnapshot(); + const last = snapshot.children?.at(-1); + expect(last?.name).to.equal('Close'); + expect(last?.role).to.equal('button'); + }); + + describe('clicking close button', function() { + beforeEach(function() { + element.focus(); + }); + beforeEach(press('ArrowLeft')); + beforeEach(press('Enter')); + beforeEach(updateComplete); + it('should remove element', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children).to.not.be.ok; + }); + }); + }); + + describe('with 4 labels and custom text attributes', function() { + beforeEach(async function() { + element = await createFixture(html` + + Label 1 + Label 2 + Label 3 + Label 4 + + `); + }); + + it('is accessible', async function() { + await expect(element).to.be.accessible(); + }); + }); + + describe('with 4 labels and `num-labels="2"` attribute', function() { + beforeEach(async function() { + element = await createFixture(html` + + Label 1 + Label 2 + Label 3 + Label 4 + + `); + }); + + it('only 2 labels should be visible', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(2); + }); + }); + + describe('with 4 labels and `num-labels="4"` attribute', function() { + beforeEach(async function() { + element = await createFixture(html` + + Label 1 + Label 2 + Label 3 + Label 4 + + `); + }); + + it('all 4 labels should be visible', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.filter(x => x.name?.startsWith('Label'))?.length).to.equal(4); + }); + }); + + describe('with category', function() { + beforeEach(async function() { + element = await createFixture(html` + + Group + Label 1 + Label 2 + + `); + }); + + it('should display the category text', async function() { + const snapshot = await a11ySnapshot(); + expect(snapshot.children?.some(x => x.name === 'Group')).to.be.true; + }); + }); +}); diff --git a/elements/pf-v5-label/README.md b/elements/pf-v5-label/README.md new file mode 100644 index 0000000000..5a235a419e --- /dev/null +++ b/elements/pf-v5-label/README.md @@ -0,0 +1,61 @@ +# Label +An inline-block element component that provides a distinct visual style for +metadata in a UI. Commonly used as visual representations for tags, labels can +include an optional pf-v5-icon and are available in a solid and outline style +variant. + +Read more about Label in the [PatternFly Elements Label documentation](https://patternflyelements.org/components/label) + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-label/pf-v5-label.js'; +``` + +## Usage + +Default +```html +

Some Text Your label text here

+``` + +Color Options: `grey (default), blue ,green, orange, red, purple, cyan` +```html +

Some Text Your label text here

+``` + +With and Icon: +```html +

Some Text Your label text here

+``` + +Outline variant: +```html +

Some Text Your label text here

+``` + +> ### Conveying meaning to assistive technologies +> Using color to add meaning only provides a visual indication, which will not be conveyed to users of assistive technologies – such as screen readers. Ensure that +> information denoted by the color is either obvious from the content itself (e.g. the visible text), or is included through alternative means, such as additional text +> hidden with the a class. + + +```html + + Some text Some additional info + +``` diff --git a/elements/pf-v5-label/demo/compact.html b/elements/pf-v5-label/demo/compact.html new file mode 100644 index 0000000000..ca72baf22a --- /dev/null +++ b/elements/pf-v5-label/demo/compact.html @@ -0,0 +1,21 @@ +
+ Default + Blue label + Green label + Orange label + Red label + Purple label + Cyan label + Gold label + Grey label +
+ + + + diff --git a/elements/pf-v5-label/demo/filled-color.html b/elements/pf-v5-label/demo/filled-color.html new file mode 100644 index 0000000000..03a80c68a5 --- /dev/null +++ b/elements/pf-v5-label/demo/filled-color.html @@ -0,0 +1,32 @@ +
+ Blue + Green + Orange + Red Hat + Purple + Cyan + Gold + Grey +
+ + + + diff --git a/elements/pf-v5-label/demo/icon.html b/elements/pf-v5-label/demo/icon.html new file mode 100644 index 0000000000..936d743edc --- /dev/null +++ b/elements/pf-v5-label/demo/icon.html @@ -0,0 +1,33 @@ +
+
+ Default + Blue label + Green label + Orange label + Red label + Purple label + Cyan label + Gold label +
+ +
+ Default + Blue label + Green label + Orange label + Red label + Purple label + Cyan label + Gold label +
+
+ + + + diff --git a/elements/pf-v5-label/demo/index.html b/elements/pf-v5-label/demo/index.html new file mode 100644 index 0000000000..a1bfef0c68 --- /dev/null +++ b/elements/pf-v5-label/demo/index.html @@ -0,0 +1,13 @@ +
+ Default +
+ + + + diff --git a/elements/pf-v5-label/demo/link.html b/elements/pf-v5-label/demo/link.html new file mode 100644 index 0000000000..d2ba371cc4 --- /dev/null +++ b/elements/pf-v5-label/demo/link.html @@ -0,0 +1,14 @@ +
+ Blue + Green + Orange + Red Hat + Purple + Cyan + Gold + Grey +
+ + diff --git a/elements/pf-v5-label/demo/outline-color.html b/elements/pf-v5-label/demo/outline-color.html new file mode 100644 index 0000000000..1627872a8c --- /dev/null +++ b/elements/pf-v5-label/demo/outline-color.html @@ -0,0 +1,20 @@ +
+ Blue + Green + Orange + Red + Purple + Cyan + Gold + Grey +
+ + + + diff --git a/elements/pf-v5-label/demo/patternfly.html b/elements/pf-v5-label/demo/patternfly.html new file mode 100644 index 0000000000..47e45906d8 --- /dev/null +++ b/elements/pf-v5-label/demo/patternfly.html @@ -0,0 +1,200 @@ +
+

Filled

+ Grey + Grey icon + Grey removable + Grey icon removable + Grey link + Grey link removable + Grey label with icon that overflows +
+ +
+

Blue

+ Blue + Blue icon + Blue removable + Blue icon removable + Blue link + Blue link removable + Blue label with icon that overflows +
+ +
+

Green

+ Green + Green icon + Green removable + Green icon removable + Green link + Green link removable + Green label with icon that overflows +
+ +
+

Orange

+ Orange + Orange icon + Orange removable + Orange icon removable + Orange link + Orange link removable + Orange label with icon that overflows +
+ +
+

Red

+ Red + Red icon + Red removable + Red icon removable + Red link + Red link removable + Red label with icon that overflows +
+ +
+

Purple

+ Purple + Purple icon + Purple removable + Purple icon removable + Purple link + Purple link removable + Purple label with icon that overflows +
+ +
+

Cyan

+ Cyan + Cyan icon + Cyan removable + Cyan icon removable + Cyan link + Cyan link removable + Cyan label with icon that overflows +
+ +
+

Gold

+ Gold + Gold icon + Gold removable + Gold icon removable + Gold link + Gold link removable + Gold label with icon that overflows +
+ +
+

Outline

+ Grey + Grey icon + Grey removable + Grey icon removable + Grey link + Grey link removable + Grey label with icon that overflows +
+ +
+

Outline Blue

+ Blue + Blue icon + Blue removable + Blue icon removable + Blue link + Blue link removable + Blue label with icon that overflows +
+ +
+

Outline Green

+ Green + Green icon + Green removable + Green icon removable + Green link + Green link removable + Green label with icon that overflows +
+ +
+

Outline Orange

+ Orange + Orange icon + Orange removable + Orange icon removable + Orange link + Orange link removable + Orange label with icon that overflows +
+ +
+

Outline Red

+ Red + Red icon + Red removable + Red icon removable + Red link + Red link removable + Red label with icon that overflows +
+ +
+

Outline Purple

+ Purple + Purple icon + Purple removable + Purple icon removable + Purple link + Purple link removable + Purple label with icon that overflows +
+ +
+

Outline Cyan

+ Cyan + Cyan icon + Cyan removable + Cyan icon removable + Cyan link + Cyan link removable + Cyan label with icon that overflows +
+ +
+

Outline Gold

+ Gold + Gold icon + Gold removable + Gold icon removable + Gold link + Gold link removable + Gold label with icon that overflows +
+ +
+

Outline Compact

+ Gold + Gold icon + Gold removable + Gold icon removable + Gold link + Gold link removable + Gold label with icon that overflows +
+ + + + diff --git a/elements/pf-label/docs/CHANGELOG.old.md b/elements/pf-v5-label/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-label/docs/CHANGELOG.old.md rename to elements/pf-v5-label/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-label/docs/pf-v5-label.md b/elements/pf-v5-label/docs/pf-v5-label.md new file mode 100644 index 0000000000..fd0467cc74 --- /dev/null +++ b/elements/pf-v5-label/docs/pf-v5-label.md @@ -0,0 +1,129 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + Default +{% endrenderOverview %} + +{% band header="Usage" %} + ### Default + {% htmlexample %} + Default + {% endhtmlexample %} + + ### With a color + Available colors are: grey (default), blue, green, orange, red, purple, cyan, gold + + {% htmlexample %} + Blue + Green + Orange + Red + Purple + Cyan + Gold + {% endhtmlexample %} + +
+ + ### Conveying meaning to assistive technologies + Using color to add meaning only provides a visual indication, which will not be conveyed to users of assistive technologies – such as screen readers. Ensure that information denoted by the color is either obvious from the content itself (e.g. the visible text), or is included through alternative means, such as additional text hidden with a class. + +
+ + {% htmlexample %} + + + Red Warning + + {% endhtmlexample %} + + ### Outline variant + Swaps the color style for a outline styled variant + + {% htmlexample %} + Blue + Green + Orange + Red + Purple + Cyan + Gold + Default + {% endhtmlexample %} + + ### With icon as an attribute + Adds a optional fixed size `pf-v5-icon` to the label as a prefix + + {% htmlexample %} + Globe + {% endhtmlexample %} + + Read more about Icon in the [PatternFly Elements Icon documentation](https://patternflyelements.org/components/icon) + + ### With user defined image using `slot="icon"` + Adds user defined SVG or `pf-v5-icon` to the label. + + {% htmlexample %} + Globe + + + {% endhtmlexample %} + + {% htmlexample %} + Globe + + + {% endhtmlexample %} + + ### Compact style + Creates a compact style label which can be combined with color, variant and icon + + {% htmlexample %} + Globe + + + Green + Orange + Red + Purple + Cyan + Gold + Default + {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-label/docs/screenshot.png b/elements/pf-v5-label/docs/screenshot.png similarity index 100% rename from elements/pf-label/docs/screenshot.png rename to elements/pf-v5-label/docs/screenshot.png diff --git a/elements/pf-v5-label/pf-v5-label.css b/elements/pf-v5-label/pf-v5-label.css new file mode 100644 index 0000000000..f40ddc3b06 --- /dev/null +++ b/elements/pf-v5-label/pf-v5-label.css @@ -0,0 +1,320 @@ +:host { + position: relative; + white-space: nowrap; + border: 0; + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--200, #8a8d90)); +} + +pf-v5-icon, ::slotted(pf-v5-icon) { + color: currentColor; +} + +:host, +#container { + display: inline-flex; + align-items: center; + vertical-align: middle; +} + +#container { + overflow: clip; + overflow-clip-margin: 6px; + text-overflow: ellipsis; + white-space: nowrap; + border-width: 0; + /** label top padding */ + padding-top: var(--pf-v5-c-label--PaddingTop, var(--pf-global--spacer--xs, 0.25rem)); + /** label left padding */ + padding-left: var(--pf-v5-c-label--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem)); + /** label bottom padding */ + padding-bottom: var(--pf-v5-c-label--PaddingBottom, var(--pf-global--spacer--xs, 0.25rem)); + /** label right padding */ + padding-right: var(--pf-v5-c-label--PaddingRight, var(--pf-global--spacer--sm, 0.5rem)); + /** label font size */ + font-size: var(--pf-v5-c-label--FontSize, var(--pf-global--FontSize--sm, 0.875rem)); + /** label text color */ + color: var(--pf-v5-c-label--Color, var(--pf-global--Color--100, #151515)); + /** label background color */ + background-color: var(--pf-v5-c-label--BackgroundColor, var(--pf-global--palette--black-150, #f5f5f5)); + /** label border radius */ + border-radius: var(--pf-v5-c-label--BorderRadius, 30em); + /** label content max width */ + max-width: var(--pf-v5-c-label__content--MaxWidth, 100%); + /** label content text color */ + color: var(--pf-v5-c-label__content--Color, var(--pf-global--Color--100, #151515)); + + --pf-global--icon--FontSize--sm: 14px; +} + +#container::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + content: ""; + border-radius: var(--pf-v5-c-label--BorderRadius, 30em); + /** label content border width and color */ + border: var(--pf-v5-c-label__content--before--BorderWidth, 1px) solid var(--pf-v5-c-label__content--before--BorderColor, var(--pf-global--palette--black-300, #d2d2d2)); +} + +[part=icon] { + display: none; + pointer-events: none; +} + +.hasIcon [part=icon] { + display: inline-flex; + width: 1em; +} + +.compact { + /** compact label top padding */ + --pf-v5-c-label--PaddingTop: var(--pf-v5-c-label--m-compact--PaddingTop, 0); + /** compact label right padding */ + --pf-v5-c-label--PaddingRight: var(--pf-v5-c-label--m-compact--PaddingRight, var(--pf-global--spacer--sm, 0.5rem)); + /** compact label bottom padding */ + --pf-v5-c-label--PaddingBottom: var(--pf-v5-c-label--m-compact--PaddingBottom, 0); + /** compact label left padding */ + --pf-v5-c-label--PaddingLeft: var(--pf-v5-c-label--m-compact--PaddingLeft, var(--pf-global--spacer--sm, 0.5rem)); + --pf-global--icon--FontSize--sm: 12px; +} + +.outline { + /** outline label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-outline--BackgroundColor, #ffffff); + --pf-v5-c-label__content--before--BorderColor: var(--pf-global--palette--black-300, #d2d2d2); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2)); +} + +.blue { + /** blue label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-blue__content--Color, var(--pf-global--info-color--200, #002952)); + /** blue label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-blue--BackgroundColor, var(--pf-global--palette--blue-50, #e7f1fa)); + /** blue label content border color */ + --pf-v5-c-label__content--before--BorderColor: var(--pf-v5-c-label--m-blue__content--before--BorderColor, var(--pf-global--palette--blue-100, #bee1f4)); + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-blue__content--link--hover--before--BorderColor, + var(--pf-global--primary-color--100, #06c))); + + --_label-icon-color: var(--pf-v5-c-label__icon--Color, var(--pf-v5-c-label--m-blue__icon--Color, var(--pf-global--primary-color--100, #06c))); +} + +.blue.outline { + /** outline blue label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-outline__content--Color, var(--pf-v5-c-label--m-outline--m-blue__content--Color, var(--pf-global--primary-color--100, #06c))); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-outline--m-blue__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2))); +} + +.cyan { + /** cyan label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-cyan__content--Color, var(--pf-global--default-color--300, #003737)); + /** cyan label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-cyan--BackgroundColor, var(--pf-global--palette--cyan-50, #f2f9f9)); + /** cyan label content border color */ + --pf-v5-c-label__content--before--BorderColor: var(--pf-v5-c-label--m-cyan__content--before--BorderColor, var(--pf-global--palette--cyan-100, #a2d9d9)); + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-cyan__content--link--hover--before--BorderColor, + var(--pf-global--default-color--200, #009596))); + + --_label-icon-color: var(--pf-v5-c-label__icon--Color, var(--pf-v5-c-label--m-cyan__icon--Color, var(--pf-global--default-color--200, #009596))); +} + +.cyan.outline { + /** outline cyan label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-outline__content--Color, var(--pf-v5-c-label--m-outline--m-cyan__content--Color, var(--pf-global--palette--cyan-400, #005f60))); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-outline--m-cyan__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2))); +} + +.green { + /** green label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-green__content--Color,var(--pf-global--success-color--200, #1e4f18)); + /** green label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-green--BackgroundColor, var(--pf-global--palette--green-50, #f3faf2)); + /** green label content border color */ + --pf-v5-c-label__content--before--BorderColor: var(--pf-v5-c-label--m-green__content--before--BorderColor, var(--pf-global--palette--green-100, #bde5b8)); + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-green__content--link--hover--before--BorderColor, + var(--pf-global--success-color--100, #3e8635))); + + --_label-icon-color: var(--pf-v5-c-label__icon--Color, var(--pf-v5-c-label--m-green__icon--Color, var(--pf-global--success-color--100, #3e8635))); +} + +.green.outline { + /** outline green label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-outline__content--Color, var(--pf-v5-c-label--m-outline--m-green__content--Color, var(--pf-global--success-color--100, #3e8635))); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-outline--m-green__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2))); +} + +.orange { + /** orange label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-orange__content--Color, var(--pf-global--palette--orange-700, #3b1f00)); + /** orange label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-orange--BackgroundColor, var(--pf-global--palette--orange-50, #fff6ec)); + /** orange label content border color */ + --pf-v5-c-label__content--before--BorderColor: var(--pf-v5-c-label--m-orange__content--before--BorderColor, var(--pf-global--palette--orange-100, #f4b678)); + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-orange__content--link--hover--before--BorderColor, + var(--pf-global--palette--orange-300, #ec7a08))); + + --_label-icon-color: var(--pf-v5-c-label__icon--Color, var(--pf-v5-c-label--m-orange__icon--Color, var(--pf-global--palette--orange-300, #ec7a08))); +} + +.orange.outline { + /** outline orange label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-outline__content--Color, var(--pf-v5-c-label--m-outline--m-orange__content--Color, var(--pf-global--palette--orange-500, #8f4700))); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-outline--m-orange__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2))); +} + +.purple { + /** purple label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-purple__content--Color, var(--pf-global--palette--purple-700, #1f0066)); + /** purple label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-purple--BackgroundColor, var(--pf-global--palette--purple-50, #f2f0fc)); + /** purple label content border color */ + --pf-v5-c-label__content--before--BorderColor: var(--pf-v5-c-label--m-purple__content--before--BorderColor, var(--pf-global--palette--purple-100, #cbc1ff)); + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-purple__content--link--hover--before--BorderColor, + var(--pf-global--palette--purple-500, #6753ac))); + + --_label-icon-color: var(--pf-v5-c-label__icon--Color, var(--pf-v5-c-label--m-purple__icon--Color, var(--pf-global--palette--purple-500, #6753ac))); +} + +.purple.outline { + /** outline purple label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-outline__content--Color, var(--pf-v5-c-label--m-outline--m-purple__content--Color, var(--pf-global--palette--purple-500, #6753ac))); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-outline--m-purple__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2))); +} + +.red { + /** red label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-red__content--Color, var(--pf-global--palette--red-300, #7d1007)); + /** red label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-red--BackgroundColor, var(--pf-global--palette--red-50, #faeae8)); + /** red label content border color */ + --pf-v5-c-label__content--before--BorderColor: var(--pf-v5-c-label--m-red__content--before--BorderColor, var(--pf-global--palette--red-100, #c9190b)); + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-red__content--link--hover--before--BorderColor, + var(--pf-global--danger-color--100, #c9190b))); + + --_label-icon-color: var(--pf-v5-c-label__icon--Color, var(--pf-v5-c-label--m-red__icon--Color, var(--pf-global--danger-color--100, #c9190b))); +} + +.red.outline { + /** outline red label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-outline__content--Color, var(--pf-v5-c-label--m-outline--m-red__content--Color, var(--pf-global--danger-color--100, #c9190b))); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-outline--m-red__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2))); +} + +.gold { + /** gold label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-gold__content--Color, var(--pf-global--palette--gold-700, #3d2c00)); + /** gold label background color */ + --pf-v5-c-label--BackgroundColor: var(--pf-v5-c-label--m-gold--BackgroundColor, var(--pf-global--palette--gold-50, #fdf7e7)); + /** gold label content border color */ + --pf-v5-c-label__content--before--BorderColor: var(--pf-v5-c-label--m-gold__content--before--BorderColor, var(--pf-global--palette--gold-100, #f9e0a2)); + + --_label-link-hover-border-color: + var(--pf-v5-c-label__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-gold__content--link--hover--before--BorderColor, + var(--pf-global--palette--gold-300, #f4c145))); + + --_label-icon-color: var(--pf-v5-c-label__icon--Color, var(--pf-v5-c-label--m-gold__icon--Color, var(--pf-global--palette--gold-400, #f0ab00))); +} + +.gold.outline { + /** outline gold label content text color */ + --pf-v5-c-label__content--Color: var(--pf-v5-c-label--m-outline__content--Color, var(--pf-v5-c-label--m-outline--m-gold__content--Color, var(--pf-global--palette--gold-600, #795600))); + + --_label-link-hover-border-color: + var(--pf-v5-c-label--m-outline__content--link--hover--before--BorderColor, + var(--pf-v5-c-label--m-outline--m-gold__content--link--hover--before--BorderColor, + var(--pf-global--BorderColor--100, #d2d2d2))); +} + +.hasIcon [part=icon] { + left: var(--pf-v5-c-label--PaddingLeft, var(--pf-global--spacer--md, 1rem)); + margin-inline-end: var(--pf-v5-c-label__icon--MarginRight, var(--pf-global--spacer--xs, 0.25rem)); + /** label icon color */ + color: var(--_label-icon-color); +} + +pf-v5-button { + --pf-v5-c-button--FontSize: var(--pf-v5-c-label__c-button--FontSize, + var(--pf-global--FontSize--xs, 0.75rem)); + --pf-v5-c-button--PaddingTop: var(--pf-v5-c-label__c-button--PaddingTop, + var(--pf-global--spacer--xs, 0.25rem)); + --pf-v5-c-button--PaddingRight: var(--pf-v5-c-label__c-button--PaddingRight, + var(--pf-global--spacer--sm, 0.5rem)); + --pf-v5-c-button--PaddingBottom: var(--pf-v5-c-label__c-button--PaddingBottom, + var(--pf-global--spacer--xs, 0.25rem)); + --pf-v5-c-button--PaddingLeft: var(--pf-v5-c-label__c-button--PaddingLeft, + var(--pf-global--spacer--sm, 0.5rem)); + margin-top: var(--pf-v5-c-label__c-button--MarginTop, -0.5rem); + margin-right: var(--pf-v5-c-label__c-button--MarginRight, -0.5rem); + margin-bottom: var(--pf-v5-c-label__c-button--MarginBottom, -0.5rem); + margin-left: var(--pf-v5-c-label__c-button--MarginLeft, 0.25rem); +} + +#link { + color: inherit; + text-decoration: none; + cursor: pointer; +} + +#container:has(#link:hover)::before, +#container:has(#link:focus)::before { + border-width: var(--pf-v5-c-label__content--link--hover--before--BorderWidth, 2px); + border-color: var(--_label-link-hover-border-color); +} + +svg { + vertical-align:-0.125em; + fill: currentColor; + height: 1em; + width: 1em; +} diff --git a/elements/pf-v5-label/pf-v5-label.ts b/elements/pf-v5-label/pf-v5-label.ts new file mode 100644 index 0000000000..d2358c129e --- /dev/null +++ b/elements/pf-v5-label/pf-v5-label.ts @@ -0,0 +1,143 @@ +import { LitElement, html, isServer, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; + +import styles from './pf-v5-label.css'; + +export class LabelCloseEvent extends Event { + constructor() { + super('close', { bubbles: true, cancelable: true }); + } +} + +/** + * The **label** component allows users to add specific element captions for user + * clarity and convenience. + * @summary Allows users to display meta data in a stylized form. + * @alias Label + * @fires {LabelCloseEvent} close - when a removable label's close button is clicked + */ +@customElement('pf-v5-label') +export class PfV5Label extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** + * Changes the style of the label. + * - Filled: Colored background with colored border. + * - Outline: White background with colored border. + */ + @property() variant: + | 'filled' + | 'outline' = 'filled'; + + /** + * Changes the color of the label + */ + @property() color: + | 'blue' + | 'cyan' + | 'green' + | 'orange' + | 'purple' + | 'red' + | 'grey' + | 'gold' = 'grey'; + + /** Shorthand for the `icon` slot, the value is icon name */ + @property() icon?: string; + + /** Flag indicating the label is compact */ + @property({ type: Boolean }) compact = false; + + /** Flag indicating the label text should be truncated */ + @property({ type: Boolean }) truncated = false; + + /** Flag indicating the label is removable */ + @property({ type: Boolean }) removable = false; + + /** Text label for a removable label's close button */ + @property({ attribute: 'close-button-label' }) closeButtonLabel?: string; + + /** When set, the label becomes a link. The label text renders inside an anchor element. */ + @property({ reflect: true }) href?: string; + + /** Represents the state of the anonymous and icon slots */ + #slots = new SlotController(this, null, 'icon'); + + override connectedCallback(): void { + super.connectedCallback(); + if (isServer) { + this.dispatchEvent(new Event('ssr:label', { bubbles: true })); + } + } + + override render(): TemplateResult<1> { + const { compact, truncated, href } = this; + const { variant, color, icon } = this; + const hasIcon = !!icon || this.#slots.hasSlotted('icon'); + const isLink = !!href; + return html` + + + + + + + ${isLink ? html` + + ` : html` + + `} + + + + + + + + + + `; + } + + #onClickClose() { + if (this.removable && this.dispatchEvent(new LabelCloseEvent())) { + this.remove(); + } + } +} + +export type LabelVariant = PfV5Label['variant']; + +export type LabelColor = PfV5Label['color']; + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-label': PfV5Label; + } +} diff --git a/elements/pf-label/test/pf-label.e2e.ts b/elements/pf-v5-label/test/pf-label.e2e.ts similarity index 95% rename from elements/pf-label/test/pf-label.e2e.ts rename to elements/pf-v5-label/test/pf-label.e2e.ts index b0e512b6dd..f75986a59b 100644 --- a/elements/pf-label/test/pf-label.e2e.ts +++ b/elements/pf-v5-label/test/pf-label.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-label'; +const tagName = 'pf-v5-label'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-label/test/pf-label.spec.ts b/elements/pf-v5-label/test/pf-label.spec.ts new file mode 100644 index 0000000000..51bd413ace --- /dev/null +++ b/elements/pf-v5-label/test/pf-label.spec.ts @@ -0,0 +1,172 @@ +import { expect, html } from '@open-wc/testing'; +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; +import { getColor, hexToRgb } from '@patternfly/pfe-tools/test/hex-to-rgb.js'; +import { PfV5Label } from '@patternfly/elements/pf-v5-label/pf-v5-label.js'; +import { PfV5Icon } from '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; + +import '@patternfly/pfe-tools/test/stub-logger.js'; + +const example = html` + Default +`; + +const exampleWithColorAttribute = html` + Red +`; + +const exampleWithColorAttributeEmpty = html` + Default +`; + +const exampleWithOutlineAttribute = html` + Default Outline +`; + +const exampleWithCompactAttribute = html` + Default Compact +`; + +const exampleWithHref = html` + Link Label +`; + + +describe('', function() { + before(function() { + // replace the default built-in icon set resolveIconName function + // with one that loads local icons. we don't want tests dependent on + // prod servers. + PfV5Icon.addIconSet('rh', function(name: string) { + return new URL(`/elements/pf-v5-icon/test/${name.replace('rh', 'rh-icon')}.svg`); + }); + }); + + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-label')).to.be.an.instanceof(PfV5Label); + }); + + it('should upgrade', async function() { + const el = await createFixture(example); + const klass = customElements.get('pf-v5-label'); + expect(el) + .to.be.an.instanceOf(klass) + .and + .to.be.an.instanceOf(PfV5Label); + }); + + it('should display default variant', async function() { + const el = await createFixture(example); + await el.updateComplete; + const container = el.shadowRoot!.querySelector('#container')!; + const beforeStyles = getComputedStyle(container, '::before'); + + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#f5f5f5')); + expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); + }); + + it('should display color variant if color attribute is present', async function() { + const el = await createFixture(exampleWithColorAttribute); + await el.updateComplete; + const container = el.shadowRoot!.querySelector('#container')!; + const beforeStyles = getComputedStyle(container, '::before'); + + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#7d1007')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#faeae8')); + expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(201, 25, 11)'); + }); + + it('should display default variant if color attribute is present but empty', async function() { + const el = await createFixture( exampleWithColorAttributeEmpty); + await el.updateComplete; + const container = el.shadowRoot!.querySelector('#container')!; + const beforeStyles = getComputedStyle(container, '::before'); + + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#f5f5f5')); + expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); + }); + + it('should display outline variant if the attribute outline is present', async function() { + const el = await createFixture(exampleWithOutlineAttribute); + await el.updateComplete; + const container = el.shadowRoot!.querySelector('#container')!; + const beforeStyles = getComputedStyle(container, '::before'); + + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#ffffff')); + expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); + }); + + describe('with valid icon attribute', function() { + let element: PfV5Label; + beforeEach(async function() { + element = await createFixture(html` + Default Icon + `); + element.updateComplete; + }); + it('should render a pf-v5-icon', async function() { + const icon = element.shadowRoot!.querySelector('pf-v5-icon')!; + expect(icon.hidden).to.be.false; + expect(icon.icon).to.equal(element.icon); + }); + }); + + describe('with empty icon attribute', function() { + let element: PfV5Label; + beforeEach(async function() { + element = await createFixture(html` + Default + `); + element.updateComplete; + }); + it('should not render a pf-v5-icon', async function() { + const icon = element.shadowRoot!.querySelector('pf-v5-icon')!; + expect(icon.hidden).to.be.true; + expect(icon.icon).to.be.undefined; + }); + }); + + it('should display compact version if the attribute is-compact is present', async function() { + const el = await createFixture(exampleWithCompactAttribute); + await el.updateComplete; + const container = el.shadowRoot!.querySelector('#container')!; + const beforeStyles = getComputedStyle(container, '::before'); + const containerStyles = getComputedStyle(container); + + expect(getColor(container, 'color')).to.deep.equal(hexToRgb('#151515')); + expect(getColor(container, 'background-color')).to.deep.equal(hexToRgb('#f5f5f5')); + expect(beforeStyles.getPropertyValue('border-color')).to.equal('rgb(210, 210, 210)'); + + expect(containerStyles.getPropertyValue('padding-top')).to.equal('0px'); + expect(containerStyles.getPropertyValue('padding-right')).to.equal('8px'); // 0.5rem = 8px @ 16px browser default + expect(containerStyles.getPropertyValue('padding-bottom')).to.equal('0px'); + expect(containerStyles.getPropertyValue('padding-left')).to.equal('8px'); // 0.5rem = 8px @ 16px browser default + }); + + describe('with href attribute', function() { + let element: PfV5Label; + beforeEach(async function() { + element = await createFixture(exampleWithHref); + await element.updateComplete; + }); + + it('should render an anchor element', function() { + const link = element.shadowRoot!.querySelector('#link'); + expect(link).to.be.an.instanceOf(HTMLAnchorElement); + }); + + it('should set the href on the anchor', function() { + const link = element.shadowRoot!.querySelector('#link') as HTMLAnchorElement; + expect(link.href).to.equal('https://example.com/'); + }); + + it('should not render an anchor when href is not set', async function() { + const el = await createFixture(example); + await el.updateComplete; + const link = el.shadowRoot!.querySelector('#link'); + expect(link).to.be.null; + }); + }); +}); diff --git a/elements/pf-v5-modal/README.md b/elements/pf-v5-modal/README.md new file mode 100644 index 0000000000..95875cfe7e --- /dev/null +++ b/elements/pf-v5-modal/README.md @@ -0,0 +1,63 @@ +# PatternFly Elements Modal + +`pf-v5-modal` is a self-contained modal window that is hidden on page load and (when activated) restricts the user from interacting with content in the main window. + +Read more about Modal in the [PatternFly Elements Modal documentation](https://patternflyelements.org/components/modal) + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-modal/pf-v5-modal.js'; +``` + +## Usage + +Open a modal dialog with the `showModal()` method, or by setting the `open` boolean attribute. + +```html + +

Modal with a header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ Learn more +
+ + +``` + +### With a trigger + +You may assign a button-like trigger element to the modal by setting the modal element's `trigger` attribute to the trigger's ID. + +```html + +

Modal with a header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ Learn more +
+ + +``` + +You may also imperatively set the trigger element with the `setTrigger()` method: +```js +const modal = document.querySelector('pf-v5-modal'); +const trigger = document.querySelector('button#my-trigger'); +modal.setTrigger(trigger); +``` + diff --git a/elements/pf-v5-modal/demo/custom-header-footer.html b/elements/pf-v5-modal/demo/custom-header-footer.html new file mode 100644 index 0000000000..3e261a9558 --- /dev/null +++ b/elements/pf-v5-modal/demo/custom-header-footer.html @@ -0,0 +1,59 @@ +
+ +

With custom header/footer content.

+

Allows for custom content in the header and/or footer by slotting HTML.

+

When static text describing the modal is available, it can be wrapped + with an ID referring to the + modal's aria-describedby value.

+

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis + aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

+ + Custom modal footer. +

+
+ Show modal +
+ + + + diff --git a/elements/pf-v5-modal/demo/custom-icon.html b/elements/pf-v5-modal/demo/custom-icon.html new file mode 100644 index 0000000000..13ef0b7292 --- /dev/null +++ b/elements/pf-v5-modal/demo/custom-icon.html @@ -0,0 +1,54 @@ +
+ +

+ + Modal Header +

+

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis + aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+ Show modal +
+ + + + diff --git a/elements/pf-modal/demo/description.html b/elements/pf-v5-modal/demo/description.html similarity index 84% rename from elements/pf-modal/demo/description.html rename to elements/pf-v5-modal/demo/description.html index 8db4de88d2..def4b6f5dc 100644 --- a/elements/pf-modal/demo/description.html +++ b/elements/pf-v5-modal/demo/description.html @@ -1,5 +1,5 @@
- +

Modal with description

A description is used when you want to provide more info about the modal than the title is able to describe. The content in the description is static and will not scroll with the rest of the modal body. @@ -31,20 +31,20 @@

Modal with description

mollis risus at ligula finibus, vitae volutpat ex auctor. Morbi vel cursus felis. Maecenas lobortis porttitor odio, non venenatis risus varius vitae. Proin gravida mi odio, sed pretium neque luctus vitae. Mauris ut libero bibendum, finibus dui non, rutrum sapien. Quisque ac bibendum erat. Vestibulum tincidunt risus nisi.

- Confirm - Cancel -
- Show modal + Confirm + Cancel + + Show modal
@@ -73,8 +73,8 @@

Modal with description

} body { - --pf-modal--MinWidth: 800px; - --pf-modal--MaxWidth: 800px; - --pf-modal--MaxHeight: 800px; + --pf-v5-modal--MinWidth: 800px; + --pf-v5-modal--MaxWidth: 800px; + --pf-v5-modal--MaxHeight: 800px; } diff --git a/elements/pf-v5-modal/demo/index.html b/elements/pf-v5-modal/demo/index.html new file mode 100644 index 0000000000..75f26f00a6 --- /dev/null +++ b/elements/pf-v5-modal/demo/index.html @@ -0,0 +1,55 @@ +
+ +

Simple modal header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum.

+ Confirm + Cancel +
+ Show modal +
+ + + + diff --git a/elements/pf-v5-modal/demo/no-header.html b/elements/pf-v5-modal/demo/no-header.html new file mode 100644 index 0000000000..8f9f731fe9 --- /dev/null +++ b/elements/pf-v5-modal/demo/no-header.html @@ -0,0 +1,49 @@ +
+ +

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis + aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+ Show modal +
+ + + + diff --git a/elements/pf-modal/demo/overflowing-content.html b/elements/pf-v5-modal/demo/overflowing-content.html similarity index 84% rename from elements/pf-modal/demo/overflowing-content.html rename to elements/pf-v5-modal/demo/overflowing-content.html index 8a4e790cc1..2ae883cc2c 100644 --- a/elements/pf-modal/demo/overflowing-content.html +++ b/elements/pf-v5-modal/demo/overflowing-content.html @@ -1,7 +1,7 @@

If the content that you're passing to the modal is likely to overflow the modal content area, it is still accessible via keyboard scrolling.

- +

Modal with overflowing content

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut @@ -30,20 +30,20 @@

Modal with overflowing content

mollis risus at ligula finibus, vitae volutpat ex auctor. Morbi vel cursus felis. Maecenas lobortis porttitor odio, non venenatis risus varius vitae. Proin gravida mi odio, sed pretium neque luctus vitae. Mauris ut libero bibendum, finibus dui non, rutrum sapien. Quisque ac bibendum erat. Vestibulum tincidunt risus nisi.

- Confirm - Cancel -
- Show modal + Confirm + Cancel + + Show modal
@@ -72,8 +72,8 @@

Modal with overflowing content

} body { - --pf-modal--MinWidth: 800px; - --pf-modal--MaxWidth: 800px; - --pf-modal--MaxHeight: 800px; + --pf-v5-modal--MinWidth: 800px; + --pf-v5-modal--MaxWidth: 800px; + --pf-v5-modal--MaxHeight: 800px; } diff --git a/elements/pf-v5-modal/demo/styling.html b/elements/pf-v5-modal/demo/styling.html new file mode 100644 index 0000000000..470867efcb --- /dev/null +++ b/elements/pf-v5-modal/demo/styling.html @@ -0,0 +1,56 @@ +
+

--pf-v5-c-modal-box--Width CSS Custom Property

+ +

Width 50% header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum.

+ Confirm + Cancel +
+ Show modal +
+ + + + diff --git a/elements/pf-v5-modal/demo/top-aligned.html b/elements/pf-v5-modal/demo/top-aligned.html new file mode 100644 index 0000000000..d02185346e --- /dev/null +++ b/elements/pf-v5-modal/demo/top-aligned.html @@ -0,0 +1,56 @@ +
+

Top aligned

+ +

Top modal header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum.

+ Confirm + Cancel +
+ Show modal +
+ + + + diff --git a/elements/pf-v5-modal/demo/trigger-attribute.html b/elements/pf-v5-modal/demo/trigger-attribute.html new file mode 100644 index 0000000000..f26847f645 --- /dev/null +++ b/elements/pf-v5-modal/demo/trigger-attribute.html @@ -0,0 +1,60 @@ +
+

You may set an external button as the trigger by setting its' ID as the modal's trigger attribute.

+ +

External trigger

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum.

+ Confirm + Cancel +
+ +

Arbitrary content can intervene between the external trigger and the modal element, as long as they are within the + same root.

+ + Show modal with external trigger +
+ + + + diff --git a/elements/pf-v5-modal/demo/variants.html b/elements/pf-v5-modal/demo/variants.html new file mode 100644 index 0000000000..211d8caab0 --- /dev/null +++ b/elements/pf-v5-modal/demo/variants.html @@ -0,0 +1,86 @@ +
+

Small

+ +

Small modal header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum.

+ Confirm + Cancel +
+ Show modal +
+ +
+

Medium

+ +

Medium modal header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum.

+ Confirm + Cancel +
+ Show modal +
+ +
+

Large

+ +

Large modal header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum.

+ Confirm + Cancel +
+ Show modal +
+ + + + diff --git a/elements/pf-v5-modal/demo/warning-alert.html b/elements/pf-v5-modal/demo/warning-alert.html new file mode 100644 index 0000000000..547dff0071 --- /dev/null +++ b/elements/pf-v5-modal/demo/warning-alert.html @@ -0,0 +1,54 @@ +
+ +

+ + Modal Header +

+

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis + aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+ Show modal +
+ + + + diff --git a/elements/pf-modal/docs/CHANGELOG.old.md b/elements/pf-v5-modal/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-modal/docs/CHANGELOG.old.md rename to elements/pf-v5-modal/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-modal/docs/pf-v5-modal.md b/elements/pf-v5-modal/docs/pf-v5-modal.md new file mode 100644 index 0000000000..dada4b71a5 --- /dev/null +++ b/elements/pf-v5-modal/docs/pf-v5-modal.md @@ -0,0 +1,62 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + +

Modal with a header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ Learn more +
+ Open modal +{% endrenderOverview %} + +{% band header="Usage" %} + {% htmlexample %} + +

Modal with a header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ Learn more +
+ Open modal + {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %} + {% htmlexample %} + +

Small modal with a header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ Learn more +
+ Open a small modal + {% endhtmlexample %} + + {% htmlexample %} + +

Medium modal with a header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ Learn more +
+ Open a medium modal + {% endhtmlexample %} + + {% htmlexample %} + +

Large modal with a header

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ Learn more +
+ Open a large modal + {% endhtmlexample %} +{% endrenderAttributes %} + +{% renderProperties %}{% endrenderProperties %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-modal/docs/screenshot.png b/elements/pf-v5-modal/docs/screenshot.png similarity index 100% rename from elements/pf-modal/docs/screenshot.png rename to elements/pf-v5-modal/docs/screenshot.png diff --git a/elements/pf-v5-modal/pf-v5-modal.css b/elements/pf-v5-modal/pf-v5-modal.css new file mode 100644 index 0000000000..26145802be --- /dev/null +++ b/elements/pf-v5-modal/pf-v5-modal.css @@ -0,0 +1,195 @@ +:host { + display: block; + position: relative; + --_spacer-align-top: var(--pf-v5-c-modal-box--m-align-top--spacer, + var(--pf-global--spacer--sm, 0.5rem)); + --_height-offset: min(var(--_spacer-align-top), var(--pf-global--spacer--2xl, 3rem)); +} + +[hidden] { + display: none !important; +} + +section { + display: flex; + position: fixed; + height: 100%; + width: 100%; + top: 0; + left: 0; + align-items: center; + justify-content: center; + /** Z-index for modal box */ + z-index: var(--pf-v5-c-modal-box--ZIndex, + var(--pf-global--ZIndex--xl, + 500)); +} + +#container { + position: relative; + max-height: inherit; +} + +[part=overlay] { + position: fixed; + height: 100%; + width: 100%; + top: 0; + left: 0; + background-color: var(--pf-v5-c-backdrop--BackgroundColor, + var(--pf-global--BackgroundColor--dark-transparent-100, + rgba(3, 3, 3, 0.62))); +} + +[part=dialog] { + position: relative; + margin: 0 auto; + + /** Width of modal */ + width: var(--pf-v5-c-modal-box--Width, + calc(100% - var(--pf-global--spacer--xl,2rem))); + + /** Max width of modal */ + max-width: var(--pf-v5-c-modal-box--MaxWidth, + calc(100% - var(--pf-global--spacer--xl,2rem))); + + /** Max height of modal */ + max-height: var(--pf-v5-c-modal-box--MaxHeight, + calc(100% - var(--pf-global--spacer--2xl,3rem))); + + /** Box shadow for modal */ + box-shadow: var(--pf-v5-c-modal-box--BoxShadow, + var(--pf-global--BoxShadow--xl, + 0 1rem 2rem 0 rgba(3, 3, 3, 0.16), + 0 0 0.5rem 0 rgba(3, 3, 3, 0.1))); + + /** Background color for modal */ + background-color: var(--pf-v5-c-modal-box--BackgroundColor, + var(--pf-global--BackgroundColor--100, + var(--pf-global--BackgroundColor--100, #fff))); + + padding: var(--pf-global--spacer--lg, 1.5rem); + margin-inline: var(--pf-global--spacer--md, 1rem); +} + +:host([width]) [part=dialog], +:host([variant]) [part=dialog] { + margin-inline: 0; +} + +:host([width="small"]) [part=dialog], +:host([variant="small"]) [part=dialog] { + /** Max width for small modal */ + --pf-v5-c-modal-box--Width: var(--pf-v5-c-modal-box--m-sm--sm--MaxWidth, 35rem); +} + +:host([width="medium"]) [part=dialog], +:host([variant="medium"]) [part=dialog] { + /** Max width for medium modal */ + --pf-v5-c-modal-box--Width: var(--pf-v5-c-modal-box--m-md--Width, 52.5rem); +} + +:host([width="large"]) [part=dialog], +:host([variant="large"]) [part=dialog] { + /** Max width for large modal */ + --pf-v5-c-modal-box--Width: var(--pf-v5-c-modal-box--m-lg--lg--MaxWidth, 70rem); +} + +[part=content] { + overflow-y: auto; + overscroll-behavior: contain; + + max-height: var(--pf-v5-c-modal-box--MaxHeight, + calc(100vh - var(--pf-global--spacer--2xl, + 3rem))); + + box-sizing: border-box; +} + +[part=content] ::slotted([slot="header"]) { + margin-top: 0 !important; +} + +header { + position: sticky; + top: 0; + background-color: var(--pf-v5-c-modal-box--BackgroundColor, + var(--pf-global--BackgroundColor--100, + var(--pf-global--BackgroundColor--100, + #fff))); +} + +header ::slotted(:is(h1,h2,h3,h4,h5,h6)[slot="header"]) { + /** Font size for modal title */ + font-size: var(--pf-v5-c-modal-box__title--FontSize, + var(--pf-global--FontSize--2xl, + 1.5rem)); + font-weight: var(--pf-global--FontWeight--normal, 400); + /** Font family for modal title */ + font-family: var(--pf-v5-c-modal-box__title--FontFamily, + var(--pf-global--FontFamily--heading--sans-serif, + var(--pf-global--FontFamily--redhat-updated--heading--sans-serif, + "RedHatDisplayUpdated", "Overpass", overpass, helvetica, arial, sans-serif))); +} + +[part=close-button] { + background-color: transparent; + border: none; + margin: 0; + padding: 0; + text-align: left; + + position: absolute; + cursor: pointer; + line-height: 24px; + padding-block: + var(--pf-v5-c-button--PaddingTop, + var(--pf-global--spacer--form-element, 0.375rem)); + padding-inline: + var(--pf-v5-c-button--PaddingRight, + var(--pf-global--spacer--md, 1rem)); + top: 0; + right: calc(var(--pf-global--spacer--lg, 1.5rem) / -3); + color: var(--pf-v5-c-button--m-plain--Color, + var(--pf-global--Color--200, + #6a6e73)); + font-size: var(--pf-v5-c-button--FontSize, + var(--pf-global--FontSize--md, + 1rem)); +} + +[part=close-button]:is(:focus-within, :focus-visible, :hover) { + color: var(--pf-v5-c-button--m-plain--focus--Color, var(--pf-global--Color--100, #151515)); +} + +[part=close-button] > svg { + font-size: 16px; + width: var(--pf-global--spacer--md, 1rem); + aspect-ratio: 1/1; +} + +:host([position="top"]) #dialog { + align-self: start; + + margin-block: var(--pf-v5-c-modal-box--m-align-top--MarginTop, + var(--pf-v5-c-modal-box--m-align-top--spacer, + 2rem)); + + margin-inline: var(--pf-global--spacer--md, 1rem); + + width: 100%; + + max-width: var(--pf-v5-c-modal-box--m-align-top--MaxWidth, + calc(100% - min( + var(--pf-v5-c-modal-box--m-align-top--spacer, 2rem) * 2, + var(--pf-global--spacer--xl, 2rem)))); + + max-height: var(--pf-v5-c-modal-box--m-align-top--MaxHeight, + calc(100% - var(--_height-offset) - var(--_spacer-align-top))); +} + +footer { + display: flex; + align-items: center; + gap: var(--pf-global--spacer--xl, 0.5rem); +} diff --git a/elements/pf-v5-modal/pf-v5-modal.ts b/elements/pf-v5-modal/pf-v5-modal.ts new file mode 100644 index 0000000000..7d83083fa5 --- /dev/null +++ b/elements/pf-v5-modal/pf-v5-modal.ts @@ -0,0 +1,320 @@ +import { LitElement, html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { query } from 'lit/decorators/query.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { ComposedEvent } from '@patternfly/pfe-core'; +import { bound, initializer, observes } from '@patternfly/pfe-core/decorators.js'; +import { getRandomId } from '@patternfly/pfe-core/functions/random.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import style from './pf-v5-modal.css'; + +export class ModalCancelEvent extends ComposedEvent { + constructor() { + super('cancel'); + } +} + +export class ModalCloseEvent extends ComposedEvent { + constructor() { + super('close'); + } +} + +export class ModalOpenEvent extends ComposedEvent { + constructor( + /** The trigger element which triggered the modal to open */ + public trigger: HTMLElement | null + ) { + super('open'); + } +} + +/** + * A **modal** displays important information to a user without requiring them to navigate + * to a new page. + * @summary Displays information or helps a user focus on a task + * @alias Modal + * @fires {ModalOpenEvent} open - Fires when a user clicks on the trigger or manually opens a modal. + * @fires {ModalCloseEvent} close - Fires when either a user clicks on either the close button or the overlay or manually closes a modal. + */ +@customElement('pf-v5-modal') +export class PfV5Modal extends LitElement { + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + static readonly styles: CSSStyleSheet[] = [style]; + + /** Should the dialog close when user clicks outside the dialog? */ + protected static closeOnOutsideClick = false; + + /** + * The `variant` controls the width of the modal. + * There are three options: `small`, `medium` and `large`. The default is `large`. + */ + @property({ reflect: true }) variant?: 'small' | 'medium' | 'large'; + + /** + * `position="top"` aligns the dialog with the top of the page + */ + @property({ reflect: true }) position?: 'top'; + + @property({ type: Boolean, reflect: true }) open = false; + + /** Optional ID of the trigger element */ + @property() trigger?: string; + + /** @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/returnValue */ + public returnValue = ''; + + @query('#overlay') private overlay?: HTMLElement | null; + @query('#dialog') private dialog?: HTMLElement | null; + @query('#close-button') private closeButton?: HTMLElement | null; + + #headerId = getRandomId(); + #triggerElement: HTMLElement | null = null; + #header: HTMLElement | null = null; + #body: Element[] = []; + #headings: Element[] = []; + #cancelling = false; + + #slots = new SlotController(this, null, 'header', 'description', 'footer'); + + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener('keydown', this.onKeydown); + this.addEventListener('click', this.onClick); + } + + render(): TemplateResult<1> { + const headerId = (this.#header || this.#headings.length) ? this.#headerId : undefined; + const headerLabel = this.#triggerElement ? this.#triggerElement.innerText : undefined; + const hasHeader = this.#slots.hasSlotted('header'); + const hasDescription = this.#slots.hasSlotted('description'); + const hasFooter = this.#slots.hasSlotted('footer'); + + return html` +
+ +
+ + +
+ `; + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + + this.removeEventListener('keydown', this.onKeydown); + + this.#triggerElement?.removeEventListener('click', this.onTriggerClick); + } + + @initializer() + protected async _init(): Promise { + await this.updateComplete; + this.#header = this.querySelector(`[slot$="header"]`); + this.#body = [...this.querySelectorAll(`*:not([slot])`)]; + this.#headings = this.#body.filter(el => el.tagName.slice(0, 1) === 'H'); + + if (this.#triggerElement) { + this.#triggerElement.addEventListener('click', this.onTriggerClick); + this.removeAttribute('hidden'); + } + + if (this.#header) { + this.#header.id = this.#headerId; + } else if (this.#headings.length > 0) { + // Get the first heading in the modal if it exists + this.#headings[0].id = this.#headerId; + } + } + + @observes('open') + protected async openChanged(oldValue?: boolean, newValue?: boolean): Promise { + // loosening types to prevent running these effects in unexpected circumstances + // eslint-disable-next-line eqeqeq + if (oldValue == null || newValue == null || oldValue == newValue) { + return; + } else if (this.open) { + // This prevents background scroll + document.body.style.overflow = 'hidden'; + await this.updateComplete; + // Set the focus to the container + this.dialog?.focus(); + this.dispatchEvent(new ModalOpenEvent(this.#triggerElement)); + } else { + // Return scrollability + document.body.style.overflow = 'auto'; + + await this.updateComplete; + + if (this.#triggerElement) { + this.#triggerElement.focus(); + } + + this.dispatchEvent(this.#cancelling ? new ModalCancelEvent() : new ModalCloseEvent()); + } + } + + @observes('trigger') + protected triggerChanged(): void { + if (this.trigger) { + this.#triggerElement = (this.getRootNode() as Document | ShadowRoot) + .getElementById(this.trigger); + this.#triggerElement?.addEventListener('click', this.onTriggerClick); + } + } + + @bound private onTriggerClick(event: MouseEvent) { + event.preventDefault(); + // TODO: in non-modal case, toggle the dialog + this.showModal(); + } + + @bound private onClick(event: MouseEvent) { + const { open, overlay, dialog } = this; + if (open) { + const path = event.composedPath(); + const { closeOnOutsideClick } = this.constructor as typeof PfV5Modal; + + if (closeOnOutsideClick && path.includes(overlay!) && !path.includes(dialog!)) { + event.preventDefault(); + this.cancel(); + } + } + } + + @bound private onKeydown(event: KeyboardEvent) { + switch (event.key) { + case 'Tab': + if (event.target === this.closeButton) { + event.preventDefault(); + this.dialog?.focus(); + } + return; + case 'Escape': + case 'Esc': + event.preventDefault(); + this.cancel(); + return; + case 'Enter': + if (event.target === this.#triggerElement) { + event.preventDefault(); + this.showModal(); + } + return; + } + } + + private async cancel() { + this.#cancelling = true; + this.open = false; + await this.updateComplete; + this.#cancelling = false; + } + + setTrigger(element: HTMLElement): void { + this.#triggerElement = element; + this.#triggerElement.addEventListener('click', this.onTriggerClick); + } + + /** + * Manually toggles the modal. + * ```js + * modal.toggle(); + * ``` + */ + @bound toggle(): void { + this.open = !this.open; + } + + /** + * Manually opens the modal. + * ```js + * modal.open(); + * ``` + */ + @bound show(): void { + this.open = true; + } + + @bound showModal(): void { + // TODO: non-modal mode + this.show(); + } + + /** + * Manually closes the modal. + * ```js + * modal.close(); + * ``` + * @param returnValue dialog return value + */ + @bound close(returnValue?: string): void { + if (typeof returnValue === 'string') { + this.returnValue = returnValue; + } + + this.open = false; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-modal': PfV5Modal; + } +} diff --git a/elements/pf-modal/test/pf-modal.e2e.ts b/elements/pf-v5-modal/test/pf-modal.e2e.ts similarity index 96% rename from elements/pf-modal/test/pf-modal.e2e.ts rename to elements/pf-v5-modal/test/pf-modal.e2e.ts index 391383f3cf..6191e4c5c0 100644 --- a/elements/pf-modal/test/pf-modal.e2e.ts +++ b/elements/pf-v5-modal/test/pf-modal.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-modal'; +const tagName = 'pf-v5-modal'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-modal/test/pf-modal.spec.ts b/elements/pf-v5-modal/test/pf-modal.spec.ts similarity index 82% rename from elements/pf-modal/test/pf-modal.spec.ts rename to elements/pf-v5-modal/test/pf-modal.spec.ts index c1115ca940..4a12e80128 100644 --- a/elements/pf-modal/test/pf-modal.spec.ts +++ b/elements/pf-v5-modal/test/pf-modal.spec.ts @@ -1,58 +1,58 @@ import { expect, nextFrame, html } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; import { setViewport } from '@web/test-runner-commands'; -import { PfModal } from '@patternfly/elements/pf-modal/pf-modal.js'; +import { PfV5Modal } from '@patternfly/elements/pf-v5-modal/pf-v5-modal.js'; import '@patternfly/pfe-tools/test/stub-logger.js'; // One element, defined here, is used // in multiple tests. It's torn down and recreated each time. const TEMPLATES = { - testElement: html``, + testElement: html``, smallModal: html` - +

Small modal

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
+
`, mediumModal: html` - +

Medium modal

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
+ `, largeModal: html` - +

Large modal

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
+ `, }; -describe('', function() { +describe('', function() { it('imperatively instantiates', function() { - expect(document.createElement('pf-modal')).to.be.an.instanceof(PfModal); + expect(document.createElement('pf-v5-modal')).to.be.an.instanceof(PfV5Modal); }); it('should upgrade', async function() { - const el = await createFixture(TEMPLATES.testElement); - expect(el, 'pf-modal should be an instance of PfModal') - .to.be.an.instanceOf(customElements.get('pf-modal')) + const el = await createFixture(TEMPLATES.testElement); + expect(el, 'pf-v5-modal should be an instance of PfV5Modal') + .to.be.an.instanceOf(customElements.get('pf-v5-modal')) .and - .to.be.an.instanceOf(PfModal); + .to.be.an.instanceOf(PfV5Modal); }); // Example test. it('should apply attributes correctly', async function() { // Use the same markup that's declared at the top of the file. - const el = await createFixture(html` - + const el = await createFixture(html` +

Modal with a header

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
+
`); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; const button = el.shadowRoot!.querySelector('[part=close-button]')!; @@ -65,12 +65,12 @@ describe('', function() { }); it('should open the modal window when the trigger is clicked', async function() { - const el = await createFixture(html` - + const el = await createFixture(html` +

Modal with a header

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
- Open a modal + + Open a modal `); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; const trigger = document.getElementById('trigger')!; @@ -88,7 +88,7 @@ describe('', function() { }); it('should remove the hidden attribute from the host on upgrade', async function() { - const el = await createFixture(TEMPLATES.testElement); + const el = await createFixture(TEMPLATES.testElement); await new Promise(r => setTimeout(r, 25)); // test for the hidden attribute on the host @@ -102,7 +102,7 @@ describe('', function() { describe('with variant=small attribute', function() { it('has small modal width', async function() { - const el = await createFixture(TEMPLATES.smallModal); + const el = await createFixture(TEMPLATES.smallModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -111,7 +111,7 @@ describe('', function() { describe('with variant=medium attribute', function() { it('has medium modal width', async function() { - const el = await createFixture(TEMPLATES.mediumModal); + const el = await createFixture(TEMPLATES.mediumModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -120,7 +120,7 @@ describe('', function() { describe('with variant=large attribute', function() { it('has large modal width', async function() { - const el = await createFixture(TEMPLATES.largeModal); + const el = await createFixture(TEMPLATES.largeModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -135,7 +135,7 @@ describe('', function() { describe('with variant=small attribute', function() { it('has small modal width', async function() { - const el = await createFixture(TEMPLATES.smallModal); + const el = await createFixture(TEMPLATES.smallModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -144,7 +144,7 @@ describe('', function() { describe('with variant=medium attribute', function() { it('has medium modal width', async function() { - const el = await createFixture(TEMPLATES.mediumModal); + const el = await createFixture(TEMPLATES.mediumModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -153,7 +153,7 @@ describe('', function() { describe('with variant=large attribute', function() { it('has large modal width', async function() { - const el = await createFixture(TEMPLATES.largeModal); + const el = await createFixture(TEMPLATES.largeModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -168,7 +168,7 @@ describe('', function() { describe('with variant=small attribute', function() { it('has small modal width', async function() { - const el = await createFixture(TEMPLATES.smallModal); + const el = await createFixture(TEMPLATES.smallModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -177,7 +177,7 @@ describe('', function() { describe('with variant=medium attribute', function() { it('has medium modal width', async function() { - const el = await createFixture(TEMPLATES.mediumModal); + const el = await createFixture(TEMPLATES.mediumModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -186,7 +186,7 @@ describe('', function() { describe('with variant=large attribute', function() { it('has large modal width', async function() { - const el = await createFixture(TEMPLATES.largeModal); + const el = await createFixture(TEMPLATES.largeModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -201,7 +201,7 @@ describe('', function() { describe('with variant=small attribute', function() { it('has small modal width', async function() { - const el = await createFixture(TEMPLATES.smallModal); + const el = await createFixture(TEMPLATES.smallModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -210,7 +210,7 @@ describe('', function() { describe('with variant=medium attribute', function() { it('has medium modal width', async function() { - const el = await createFixture(TEMPLATES.mediumModal); + const el = await createFixture(TEMPLATES.mediumModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); @@ -219,7 +219,7 @@ describe('', function() { describe('with variant=large attribute', function() { it('has large modal width', async function() { - const el = await createFixture(TEMPLATES.largeModal); + const el = await createFixture(TEMPLATES.largeModal); const modalWindow = el.shadowRoot!.querySelector('#dialog')!; expect(getComputedStyle(modalWindow).getPropertyValue('max-width')) .to.equal('calc(100% - 32px)'); diff --git a/elements/pf-v5-panel/README.md b/elements/pf-v5-panel/README.md new file mode 100644 index 0000000000..5c2ec71f2d --- /dev/null +++ b/elements/pf-v5-panel/README.md @@ -0,0 +1,10 @@ +# Panel +Panel contains content with optional header and footer content. + +## Usage + +```html + +

Panel content

+
+``` diff --git a/elements/pf-v5-panel/demo/bordered.html b/elements/pf-v5-panel/demo/bordered.html new file mode 100644 index 0000000000..1d521901ce --- /dev/null +++ b/elements/pf-v5-panel/demo/bordered.html @@ -0,0 +1,15 @@ +
+ +

Main content

+
+
+ + + + diff --git a/elements/pf-v5-panel/demo/footer.html b/elements/pf-v5-panel/demo/footer.html new file mode 100644 index 0000000000..815710f86b --- /dev/null +++ b/elements/pf-v5-panel/demo/footer.html @@ -0,0 +1,16 @@ +
+ +

Main content

+

Footer content

+
+
+ + + + diff --git a/elements/pf-v5-panel/demo/header-and-footer.html b/elements/pf-v5-panel/demo/header-and-footer.html new file mode 100644 index 0000000000..48f3f3874b --- /dev/null +++ b/elements/pf-v5-panel/demo/header-and-footer.html @@ -0,0 +1,17 @@ +
+ +

Header content

+

Main content

+

Footer content

+
+
+ + + + diff --git a/elements/pf-v5-panel/demo/header.html b/elements/pf-v5-panel/demo/header.html new file mode 100644 index 0000000000..34e268fe27 --- /dev/null +++ b/elements/pf-v5-panel/demo/header.html @@ -0,0 +1,16 @@ +
+ +

Header content

+

Main content

+
+
+ + + + diff --git a/elements/pf-v5-panel/demo/index.html b/elements/pf-v5-panel/demo/index.html new file mode 100644 index 0000000000..4719a87801 --- /dev/null +++ b/elements/pf-v5-panel/demo/index.html @@ -0,0 +1,15 @@ +
+ +

Main content

+
+
+ + + + diff --git a/elements/pf-v5-panel/demo/raised.html b/elements/pf-v5-panel/demo/raised.html new file mode 100644 index 0000000000..57b05b32d6 --- /dev/null +++ b/elements/pf-v5-panel/demo/raised.html @@ -0,0 +1,15 @@ +
+ +

Main content

+
+
+ + + + diff --git a/elements/pf-panel/demo/scrollable-with-header-and-footer.html b/elements/pf-v5-panel/demo/scrollable-with-header-and-footer.html similarity index 95% rename from elements/pf-panel/demo/scrollable-with-header-and-footer.html rename to elements/pf-v5-panel/demo/scrollable-with-header-and-footer.html index ca0f4c4c18..75d223a4e8 100644 --- a/elements/pf-panel/demo/scrollable-with-header-and-footer.html +++ b/elements/pf-v5-panel/demo/scrollable-with-header-and-footer.html @@ -1,5 +1,5 @@
- +

Header content

Free software is absolutely critical to the way I perform daily @@ -42,11 +42,11 @@

Header content

Morgan Deters

Footer content

-
+
diff --git a/elements/pf-v5-popover/demo/edge-of-viewport.html b/elements/pf-v5-popover/demo/edge-of-viewport.html new file mode 100644 index 0000000000..9bf2a1eea3 --- /dev/null +++ b/elements/pf-v5-popover/demo/edge-of-viewport.html @@ -0,0 +1,29 @@ +

+ When the viewport is 1190 pixels wide, (such that the "toggle popover" button appears + at the far edge of the viewport, the popover at the far end of the viewport + must not cause a horizontal scrollbar which would otherwise not have been there. +

+Spacer +Spacer +Spacer +Spacer +Spacer +Spacer +Spacer +Spacer +Spacer +Spacer +Spacer +Spacer + +

In this popover, the content is quite wide, + so as to force a horizontal scrollbar if the + dialog content is improperly rendered.

+ Toggle popover +
+ + + diff --git a/elements/pf-v5-popover/demo/flip.html b/elements/pf-v5-popover/demo/flip.html new file mode 100644 index 0000000000..fae46422dc --- /dev/null +++ b/elements/pf-v5-popover/demo/flip.html @@ -0,0 +1,117 @@ +
+
+ No flip + + Toggle popover + +
+ +
+ Flip fallback + + + Toggle popover + +
+
+ + + + diff --git a/elements/pf-v5-popover/demo/icons.html b/elements/pf-v5-popover/demo/icons.html new file mode 100644 index 0000000000..7a299ea8c2 --- /dev/null +++ b/elements/pf-v5-popover/demo/icons.html @@ -0,0 +1,53 @@ +
+
+ Icon attribute + + Toggle popover + +
+ +
+ Icon slot + + +

Popover with icon

+
Popovers are triggered by click rather than hover.
+
Popover footer
+ Toggle popover +
+
+ +
+ Custom Icon Set + + +
Popover heading
+
+ Popovers are triggered by click rather than hover. +
+ Popover footer + Toggle popover +
+
+
+ + + + diff --git a/elements/pf-v5-popover/demo/index.html b/elements/pf-v5-popover/demo/index.html new file mode 100644 index 0000000000..4d60931069 --- /dev/null +++ b/elements/pf-v5-popover/demo/index.html @@ -0,0 +1,91 @@ +
+
+ Triggered by reference + + + Toggle popover +
+ +
+ No hide on outside click + + Toggle popover + +
+ +
+ Close popover from content + +

Popover heading

+
Popovers are triggered by click rather than hover.
+ + Close popover + + Toggle popover +
+
+ +
+ Without header, footer, or close + +
+ This popover has no padding and is intended for use with content that has its own spacing and should touch the + edges of the popover container. +
+ Toggle popover +
+
+ +
+ Width auto + +
Removes fixed-width and allows width to be defined by contents.
+ Toggle popover +
+
+
+ + + + diff --git a/elements/pf-v5-popover/docs/pf-v5-popover.md b/elements/pf-v5-popover/docs/pf-v5-popover.md new file mode 100644 index 0000000000..f5a9bd4a0c --- /dev/null +++ b/elements/pf-v5-popover/docs/pf-v5-popover.md @@ -0,0 +1,136 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + + + Toggle popover + + +{% endrenderOverview %} + +{% band header="Usage" %} + {% htmlexample %} + + Toggle popover + + {% endhtmlexample %} + + **Note**: Unlike the [Patternfly React implementation][withfocustrap], this + component does not trap focus in the popover dialog. If you would like to trap + focus, consider using a modal dialog instead. + + ### Activating programmatically + + Use the `show()` method to activate the popover. + + + HTML + + +```html + + +Hover to cite + + + Richard M. Stallman + Free software is a political movement; open source is a development model. + +``` + + + Lit + + +```js +import { LitElement, html } from 'lit'; +import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; +import '@patternfly/elements/pf-v5-popover/pf-v5-popover.js'; + +class Citer extends LitElement { + render() { + return html` + Hover to Cite + + + Richard M. Stallman + Free software is a political movement; open source is a development model. + + `; + } + + get #popover() { return this.shadowRoot.querySelector('pf-v5-popover'); } + + #onMouseover() { this.#popover.show(); } + + #onMouseout() { this.#popover.hide(); } +} +``` + + + React + + +```jsx +import { Button } from '@patternfly/elements/react/pf-v5-button/pf-v5-button.js'; +import { Popover } from '@patternfly/elements/react/pf-v5-popover/pf-v5-popover.js'; +import { useRef } from 'react'; + + +export function Citer() { + const popoverRef = useRef(null); + + const onMouseover = e => void popoverRef.current.show(); + + const onMouseout = e => void popoverRef.current.hide(); + + return ( + <> + + + Richard M. Stallman + Free software is a political movement; open source is a development model. + + + ); +} +``` + + + +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} + +[withfocustrap]: https://www.patternfly.org/v4/components/popover#:~:textwithfocustrap diff --git a/elements/pf-popover/docs/screenshot.png b/elements/pf-v5-popover/docs/screenshot.png similarity index 100% rename from elements/pf-popover/docs/screenshot.png rename to elements/pf-v5-popover/docs/screenshot.png diff --git a/elements/pf-v5-popover/pf-v5-popover.css b/elements/pf-v5-popover/pf-v5-popover.css new file mode 100644 index 0000000000..621f623a79 --- /dev/null +++ b/elements/pf-v5-popover/pf-v5-popover.css @@ -0,0 +1,224 @@ +:host { + display: inline; + /** Width of the arrow */ + --_floating-arrow-size: var(--pf-v5-c-popover__arrow--Width, var(--pf-global--arrow--width-lg, 1.5625rem)); + /** Heading font color */ + --_header-text-color: var(--pf-v5-c-popover__title-text--Color, inherit); + /** Heading icon font color */ + --_header-icon-color: var(--pf-v5-c-popover__title-icon--Color, var(--pf-global--Color--100, #151515)); + --_animation-speed: var(--pf-v5-popover--animation-speed, 300ms); + --_z-index: var(--pf-v5-popover--z-index, 9999); +} + +.visually-hidden { + position: fixed; + top: 0; + left: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +[hidden] { + display: none !important; +} + +#container { + display: inline-flex; + position: relative; +} + +#trigger { + display: inline-block; + position: relative; +} + +#arrow { + display: block; + position: absolute; + /** Arrow background color */ + background-color: var(--pf-v5-c-popover__arrow--BackgroundColor, var(--pf-global--BackgroundColor--100, #fff)); + /** Arrow box shadow */ + box-shadow: var( + --pf-v5-c-popover__arrow--BoxShadow, + var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)) + ); + content: ''; + /** Height of the arrow */ + height: var(--pf-v5-c-popover__arrow--Height, var(--pf-global--arrow--width-lg, 1.5625rem)); + /** Width of the arrow */ + width: var(--pf-v5-c-popover__arrow--Width, var(--pf-global--arrow--width-lg, 1.5625rem)); + rotate: 45deg; + z-index: -1; + pointer-events: none; +} + +#popover { + display: block; + position: absolute; + opacity: 0; + z-index: -1; + transition: visibility 0s, opacity var(--_animation-speed) cubic-bezier(0.54, 1.5, 0.38, 1.11) 0s; + left: 0; + top: 0; + translate: var(--_floating-content-translate); + /** Popover box shadow */ + box-shadow: var( + --pf-v5-c-popover--BoxShadow, + var(--pf-global--BoxShadow--lg, 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.16), 0 0 0.375rem 0 rgba(3, 3, 3, 0.08)) + ); + border: 0; + padding: 0; + visibility: hidden; +} + +#popover[open] { + opacity: 1; + z-index: var(--_z-index); + visibility: visible; +} + +[part='content'] { + position: relative; + /** Popover padding (top, right, bottom, left) */ + padding: var(--pf-v5-c-popover__content--PaddingTop, var(--pf-global--spacer--md, 1rem)) + var(--pf-v5-c-popover__content--PaddingRight, var(--pf-global--spacer--md, 1rem)) + var(--pf-v5-c-popover__content--PaddingBottom, var(--pf-global--spacer--md, 1rem)) + var(--pf-v5-c-popover__content--PaddingLeft, var(--pf-global--spacer--md, 1rem)); + word-break: break-word; + /** Popover line height */ + line-height: var(--pf-v5-c-popover--line-height, 1.5); + /** Popover font-size */ + font-size: var(--pf-v5-c-popover__content--FontSize, var(--pf-global--FontSize--sm, 0.875rem)); + color: var(--pf-v5-c-popover__content--Color, var(--pf-global--Color--100, #151515)); + /** Popover background color */ + background-color: var(--pf-v5-c-popover__content--BackgroundColor, var(--pf-global--BackgroundColor--100, #fff)); + /** Popover max-width */ + max-width: var( + --pf-v5-c-popover--MaxWidth, + calc(var(--pf-v5-c-popover__content--PaddingLeft, 1rem) + var(--pf-v5-c-popover__content--PaddingRight, 1rem) + 18.75rem) + ); + /** Popover min-width */ + min-width: var( + --pf-v5-c-popover--MinWidth, + calc(var(--pf-v5-c-popover__content--PaddingLeft, 1rem) + var(--pf-v5-c-popover__content--PaddingRight, 1rem) + 18.75rem) + ); + width: max-content; +} + +[part='close-button'] { + cursor: pointer; + position: absolute; + /** Close button right position */ + right: var( + --pf-v5-c-popover--c-button--Right, + calc(var(--pf-v5-c-popover__content--PaddingRight, 1rem) - var(--pf-global--spacer--md, 1rem)) + ); + /** Close button top position */ + top: var( + --pf-v5-c-popover--c-button--Top, + calc(var(--pf-v5-c-popover__content--PaddingTop, 1rem) - var(--pf-global--spacer--form-element, 0.375rem)) + ); +} + +[part='content'] > [part='close-button']:not([hidden]) ~ *:not([hidden]) { + /** Padding between close button and its immediate sibling */ + padding-right: var(--pf-v5-c-popover--c-button--sibling--PaddingRight, var(--pf-global--spacer--2xl, 3rem)); +} + +[part='header'] { + display: flex; + align-items: baseline; +} + +[part='icon'] { + color: var(--_header-icon-color); + /** Heading icon right margin */ + margin-right: var(--pf-v5-c-popover__title-icon--MarginRight, var(--pf-global--spacer--sm, 0.5rem)); +} + +[part='icon'] ::slotted(*), +[part='icon'] * { + vertical-align: -0.125em; +} + +[part='icon'], +[part='heading']::slotted(:is(h2, h3, h4, h5, h6)), +[part='heading'] :is(h2, h3, h4, h5, h6) { + /** Header font-size */ + font-size: var(--pf-v5-c-popover__title--FontSize, var(--pf-global--FontSize--md, 1rem)); + font-weight: var(--pf-global--FontWeight--normal, 400); + --pf-v5-icon--size: var( + --pf-v5-c-popover__title--FontSize, + var(--pf-global--FontSize--md, var(--pf-global--icon--FontSize--md, 1em)) + ); +} + +[part='heading']::slotted(:is(h2, h3, h4, h5, h6)), +[part='heading'] :is(h2, h3, h4, h5, h6) { + color: var(--_header-text-color); + margin-top: 0; + /** Header bottom margin */ + margin-bottom: var(--pf-v5-c-popover__title--MarginBottom, var(--pf-global--spacer--sm, 0.5rem)); + /** Header line height */ + line-height: var(--pf-v5-c-popover__title--LineHeight, var(--pf-global--LineHeight--md, 1.5)); + /** Header font-family */ + font-family: var( + --pf-v5-c-popover__title--FontFamily, + var( + --pf-global--FontFamily--heading--sans-serif, + 'RedHatDisplay', + 'Overpass', + overpass, + helvetica, + arial, + sans-serif + ) + ); +} + +[part='body'] { + display: block; + word-wrap: break-word; +} + +[part='footer'] { + /** Footer top margin */ + margin-top: var(--pf-v5-c-popover__footer--MarginTop, var(--pf-global--spacer--md, 1rem)); +} + +:host([alert-severity='default']) { + /** Default alert heading color */ + --_header-text-color: var(--pf-v5-c-popover--m-default__title-text--Color, var(--pf-global--default-color--300, #003737)); + /** Default alert icon color */ + --_header-icon-color: var(--pf-v5-c-popover--m-default__title-icon--Color, var(--pf-global--default-color--200, #009596)); +} + +:host([alert-severity='info']) { + /** Info alert heading color */ + --_header-text-color: var(--pf-v5-c-popover--m-info__title-text--Color, var(--pf-global--info-color--200, #002952)); + /** Info alert icon color */ + --_header-icon-color: var(--pf-v5-c-popover--m-info__title-icon--Color, var(--pf-global--info-color--100, #2b9af3)); +} + +:host([alert-severity='warning']) { + /** Warning alert icon color */ + --_header-icon-color: var(--pf-v5-c-popover--m-warning__title-icon--Color, var(--pf-global--warning-color--100, #f0ab00)); + /** Warning alert heading color */ + --_header-text-color: var(--pf-v5-c-popover--m-warning__title-text--Color, var(--pf-global--warning-color--200, #795600)); +} + +:host([alert-severity='success']) { + /** Success alert icon color */ + --_header-icon-color: var(--pf-v5-c-popover--m-success__title-icon--Color, var(--pf-global--success-color--100, #3e8635)); + /** Success alert heading color */ + --_header-text-color: var(--pf-v5-c-popover--m-success__title-text--Color, var(--pf-global--success-color--200, #1e4f18)); +} + +:host([alert-severity='danger']) { + /** Danger alert icon color */ + --_header-icon-color: var(--pf-v5-c-popover--m-danger__title-icon--Color, var(--pf-global--danger-color--100, #c9190b)); + /** Danger alert heading color */ + --_header-text-color: var(--pf-v5-c-popover--m-danger__title-text--Color, var(--pf-global--danger-color--200, #a30000)); +} diff --git a/elements/pf-v5-popover/pf-v5-popover.ts b/elements/pf-v5-popover/pf-v5-popover.ts new file mode 100644 index 0000000000..565c97a63a --- /dev/null +++ b/elements/pf-v5-popover/pf-v5-popover.ts @@ -0,0 +1,415 @@ +import type { Placement } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; + +import { LitElement, nothing, html, type PropertyValues, isServer, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { query } from 'lit/decorators/query.js'; +import { styleMap } from 'lit/directives/style-map.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; + +import { bound } from '@patternfly/pfe-core/decorators/bound.js'; +import { ComposedEvent, StringListConverter } from '@patternfly/pfe-core/core.js'; + +import '@patternfly/elements/pf-v5-button/pf-v5-button.js'; + +import styles from './pf-v5-popover.css'; + +type HeadingLevel = 2 | 3 | 4 | 5 | 6; + +type AlertSeverity = 'default' | 'info' | 'warning' | 'success' | 'danger'; + +export class PopoverHideEvent extends ComposedEvent { + constructor() { + super('hide'); + } +} + +export class PopoverHiddenEvent extends ComposedEvent { + constructor() { + super('hidden'); + } +} + +export class PopoverShowEvent extends ComposedEvent { + constructor() { + super('show'); + } +} + +export class PopoverShownEvent extends ComposedEvent { + constructor() { + super('shown'); + } +} + +/** + * A **Popover** displays content in a non-modal dialog and adds contextual information or provides resources via text and links. + * @summary Toggle the visibility of helpful or contextual information. + * @alias Popover + */ +@customElement('pf-v5-popover') +export class PfV5Popover extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + private static instances = new Set(); + + private static alertIcons = new Map(Object.entries({ + default: 'bell', + info: 'circle-info', + success: 'circle-check', + warning: 'triangle-exclamation', + danger: 'circle-exclamation', + } satisfies Record) as [AlertSeverity, string][]); + + static { + if (!isServer) { + document.addEventListener('click', function(event) { + for (const instance of PfV5Popover.instances) { + if (!instance.noOutsideClick) { + instance.#outsideClick(event); + } + } + }); + } + } + + /** + * Indicates the initial popover position. + * There are 12 options: `top`, `bottom`, `left`, `right`, `top-start`, `top-end`, + * `bottom-start`, `bottom-end`, `left-start`, `left-end`,`right-start`, `right-end`. + * The default is `top`. + */ + @property({ reflect: true }) position: Placement = 'top'; + + /** + * The content rendered in the popover's heading. + */ + @property({ reflect: true }) heading?: string; + + /** + * The content rendered in the popover's body. + */ + @property({ reflect: true }) body?: string; + + /** + * The content rendered in the popover's footer. + */ + @property({ reflect: true }) footer?: string; + + /** + * The icon placed before the popover's heading. + */ + @property({ reflect: true }) icon?: string; + + /** + * The accessible label for the popover. This is required if the no heading is set. + */ + @property({ reflect: true }) label?: string; + + /** + * The distance to set between the popover and its trigger element. + */ + @property({ type: Number, reflect: true }) distance?: number; + + /** + * The flip order when flip is enabled and the initial position is not possible. + * There are 12 options: `top`, `bottom`, `left`, `right`, `top-start`, `top-end`, + * `bottom-start`, `bottom-end`, `left-start`, `left-end`,`right-start`, `right-end`. + * The default is [oppositePlacement], where only the opposite placement is tried. + */ + @property({ + attribute: 'flip-behavior', + converter: StringListConverter, + }) flipBehavior?: Placement[]; + + /** + * Disable the flip behavior. The default is `false`. + */ + @property({ type: Boolean, reflect: true, attribute: 'no-flip' }) noFlip = false; + + /** + * The heading level to use for the popover's header. The default is `h6`. + */ + @property({ + type: Number, + reflect: true, + attribute: 'heading-level', + }) headingLevel?: HeadingLevel; + + /** + * Indicates which icon set to use for the header's icon. + * The default is `fas` (Font Awesome Free Solid). + */ + @property({ reflect: true, attribute: 'icon-set' }) iconSet?: string; + + /** + * Hide the close button. The default is `false`. + */ + @property({ type: Boolean, reflect: true, attribute: 'hide-close' }) hideClose?: boolean; + + /** + * Indicates the severity variant to use for the alert popover. + * There are five options: `default`, `info`, `warning`, `success`, and `danger`. + */ + @property({ reflect: true, attribute: 'alert-severity' }) alertSeverity?: AlertSeverity; + + /** + * The accessible label for the popover's close button. The default is `Close popover`. + */ + @property({ reflect: true, attribute: 'accessible-close-label' }) accessibleCloseLabel?: string; + + + /** + * The text announced by the screen reader to indicate the popover's severity. + * The default is `${alertSeverity} alert:`. + */ + @property({ reflect: true, attribute: 'alert-severity-text' }) alertSeverityText?: string; + + /** + * Don't hide the popover when clicking ouside of it. + */ + @property({ + type: Boolean, + reflect: true, + attribute: 'no-outside-click', + }) noOutsideClick?: boolean; + + /** + * The ID of the element to attach the popover to. + */ + @property({ reflect: true }) trigger?: string; + + @query('#popover') private _popover!: HTMLDialogElement; + @query('#trigger') private _slottedTrigger?: HTMLElement | null; + @query('#arrow') private _arrow!: HTMLDivElement; + + /** True before the show animation begins and after the hide animation ends */ + #hideDialog = true; + + #referenceTrigger?: HTMLElement | null = null; + + #float = new FloatingDOMController(this, { + content: () => this._popover, + arrow: () => this._arrow, + invoker: () => this.#referenceTrigger || this._slottedTrigger, + }); + + #slots = new SlotController(this, null, 'icon', 'heading', 'body', 'footer'); + + constructor() { + super(); + if (!isServer) { + this.addEventListener('keydown', this.#onKeydown); + } + } + + render(): TemplateResult<1> { + const { alignment, anchor, styles } = this.#float; + const hasFooter = this.#slots.hasSlotted('footer') || !!this.footer; + const hasHeading = this.#slots.hasSlotted('heading') || !!this.heading; + const hasIcon = this.#slots.hasSlotted('icon') || !!this.icon || !!this.alertSeverity; + + // https://github.com/asyncLiz/minify-html-literals/issues/37 + let headingContent = html`
${this.heading ?? ''}
`; + switch (this.headingLevel) { + case 2: headingContent = html`

${this.heading ?? ''}

`; break; + case 3: headingContent = html`

${this.heading ?? ''}

`; break; + case 4: headingContent = html`

${this.heading ?? ''}

`; break; + case 5: headingContent = html`
${this.heading ?? ''}
`; break; + } + + const headingSlotWithFallback = html` + + ${headingContent} + `; + + const headerIcon = this.icon + ?? PfV5Popover.alertIcons.get(this.alertSeverity as AlertSeverity) + ?? ''; + + return html` + +
+ + + +
+ +
+ + + + + + + ${!(hasHeading && hasIcon) ? headingSlotWithFallback : html` + +
+ + + + + + + ${!this.alertSeverity ? nothing : html` + ${this.alertSeverityText ?? `${this.alertSeverity} alert:`}`} + ${headingSlotWithFallback} +
`} + + ${this.body ?? ''} + +
+ + ${this.footer} +
+
+
+
+ `; + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + PfV5Popover.instances.delete(this); + this.#referenceTrigger?.removeEventListener('click', this.toggle); + this.#referenceTrigger?.removeEventListener('keydown', this.#onKeydown); + } + + #getReferenceTrigger() { + if (isServer || !this.trigger) { + return null; + } else { + return (this.getRootNode() as Document | ShadowRoot).getElementById(this.trigger); + } + } + + #triggerChanged() { + const oldReferenceTrigger = this.#referenceTrigger; + this.#referenceTrigger = this.#getReferenceTrigger(); + if (oldReferenceTrigger !== this.#referenceTrigger) { + oldReferenceTrigger?.removeEventListener('click', this.toggle); + oldReferenceTrigger?.removeEventListener('keydown', this.#onKeydown); + this.#referenceTrigger?.addEventListener('click', this.toggle); + this.#referenceTrigger?.addEventListener('keydown', this.#onKeydown); + } + } + + #onKeydown = (event: KeyboardEvent) => { + switch (event.key) { + case 'Escape': + case 'Esc': + event.preventDefault(); + this.hide(); + return; + case 'Enter': + if (event.target === this.#referenceTrigger || event.target === this._slottedTrigger) { + event.preventDefault(); + this.show(); + } + return; + } + }; + + #outsideClick(event: MouseEvent) { + if (this.#hideDialog) { + return; + } + const path = event.composedPath(); + if (!path.includes(this) && !path.includes(this.#referenceTrigger as HTMLElement)) { + this.hide(); + } + } + + /** + * Removes event listeners from the old trigger element and attaches + * them to the new trigger element. + * @param changed changed props + */ + override willUpdate(changed: PropertyValues): void { + if (changed.has('trigger')) { + this.#triggerChanged(); + } + } + + /** + * Toggle the popover + */ + @bound async toggle(): Promise { + if (this.#float.open) { + this.hide(); + } else { + this.show(); + } + } + + /** + * Opens the popover + */ + @bound async show(): Promise { + this.#hideDialog = false; + this.requestUpdate(); + this.dispatchEvent(new PopoverShowEvent()); + await this.updateComplete; + await this.#float.show({ + offset: this.distance ?? 25, + placement: this.position, + flip: !this.noFlip, + fallbackPlacements: this.flipBehavior, + }); + this._popover?.show(); + this.dispatchEvent(new PopoverShownEvent()); + PfV5Popover.instances.add(this); + } + + /** + * Closes the popover + */ + @bound async hide(): Promise { + this.dispatchEvent(new PopoverHideEvent()); + await this.#float.hide(); + this._popover?.close(); + this.dispatchEvent(new PopoverHiddenEvent()); + PfV5Popover.instances.delete(this); + this.#hideDialog = true; + this.requestUpdate(); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-popover': PfV5Popover; + } +} diff --git a/elements/pf-popover/test/pf-popover.e2e.ts b/elements/pf-v5-popover/test/pf-popover.e2e.ts similarity index 95% rename from elements/pf-popover/test/pf-popover.e2e.ts rename to elements/pf-v5-popover/test/pf-popover.e2e.ts index 78709c9997..2033dd201e 100644 --- a/elements/pf-popover/test/pf-popover.e2e.ts +++ b/elements/pf-v5-popover/test/pf-popover.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-popover'; +const tagName = 'pf-v5-popover'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-popover/test/pf-popover.spec.ts b/elements/pf-v5-popover/test/pf-popover.spec.ts similarity index 86% rename from elements/pf-popover/test/pf-popover.spec.ts rename to elements/pf-v5-popover/test/pf-popover.spec.ts index bd44cd1722..3c8ac3daba 100644 --- a/elements/pf-popover/test/pf-popover.spec.ts +++ b/elements/pf-v5-popover/test/pf-popover.spec.ts @@ -2,8 +2,8 @@ import { expect, html, fixture, fixtureCleanup, nextFrame } from '@open-wc/testi import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; import { clickElementAtCenter } from '@patternfly/pfe-tools/test/utils.js'; import { sendKeys, resetMouse } from '@web/test-runner-commands'; -import { PfPopover } from '@patternfly/elements/pf-popover/pf-popover.js'; -import { PfButton } from '@patternfly/elements/pf-button/pf-button.js'; +import { PfV5Popover } from '@patternfly/elements/pf-v5-popover/pf-v5-popover.js'; +import { PfV5Button } from '@patternfly/elements/pf-v5-button/pf-v5-button.js'; function press(key: string) { return async function() { @@ -11,12 +11,12 @@ function press(key: string) { }; } -describe('', function() { - let element: PfPopover; +describe('', function() { + let element: PfV5Popover; /** create a simple test fixture */ async function setupSimpleInstance() { - element = await fixture(html``); + element = await fixture(html``); } /** Wait on the element's update cycle */ @@ -30,7 +30,7 @@ describe('', function() { } function resetElement() { - document.querySelectorAll('pf-popover').forEach(e => e.remove()); + document.querySelectorAll('pf-v5-popover').forEach(e => e.remove()); // @ts-expect-error: resetting test state, so we don't mind the ts error. element = undefined; } @@ -41,16 +41,16 @@ describe('', function() { describe('simply instantiating', function() { beforeEach(setupSimpleInstance); it('should upgrade', async function() { - const klass = customElements.get('pf-popover'); + const klass = customElements.get('pf-v5-popover'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfPopover); + .to.be.an.instanceOf(PfV5Popover); }); it('should be accessible', expectA11yAxe); it('imperatively instantiates', function() { - expect(document.createElement('pf-popover')) - .to.be.an.instanceof(PfPopover); + expect(document.createElement('pf-v5-popover')) + .to.be.an.instanceof(PfV5Popover); }); it('should not report anything to assistive technology', async function() { const snapshot = await a11ySnapshot(); @@ -69,12 +69,12 @@ describe('', function() { /** create a test fixture with slotted trigger and content attrs */ beforeEach(async function setupPopoverWithSlottedTriggerAndContentAttrs() { - element = await fixture(html` - (html` + - Toggle popover - + Toggle popover + `); }); @@ -88,12 +88,12 @@ describe('', function() { describe('tabbing to the trigger', function() { beforeEach(resetElement); beforeEach(async function setupPopoverWithSlottedTriggerAndContentAttrs() { - element = await fixture(html` - (html` + - Toggle popover - + Toggle popover + `); }); @@ -102,7 +102,7 @@ describe('', function() { beforeEach(updateComplete); it('doesn\'t steal tab order', function() { - expect(document.activeElement).to.be.an.instanceof(PfButton); + expect(document.activeElement).to.be.an.instanceof(PfV5Button); }); describe('and pressing Enter', function() { @@ -156,16 +156,16 @@ describe('', function() { beforeEach(async function() { const container = await fixture(html`
- + footer="Popover footer">
`); - element = container.querySelector('pf-popover')!; + element = container.querySelector('pf-v5-popover')!; btn1 = container.querySelector('#btn-1')!; btn2 = container.querySelector('#btn-2')!; }); diff --git a/elements/pf-v5-progress-stepper/README.md b/elements/pf-v5-progress-stepper/README.md new file mode 100644 index 0000000000..c1bf817dc6 --- /dev/null +++ b/elements/pf-v5-progress-stepper/README.md @@ -0,0 +1,41 @@ +# PatternFly Elements Progress Stepper + +A component that gives the user a visual representation of the current state of +their progress through an application (typically a multistep form). + +Read more about Progress Stepper in the [PatternFly Elements Progress Steps +documentation][docs]. + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.js'; +``` + +## Usage + +```html + + Completed + Issue + Failure + Running + Last + +``` + +[docs]: https://patternflyelements.org/components/progress-stepper diff --git a/elements/pf-v5-progress-stepper/demo/alignment.html b/elements/pf-v5-progress-stepper/demo/alignment.html new file mode 100644 index 0000000000..77e9fdbfe5 --- /dev/null +++ b/elements/pf-v5-progress-stepper/demo/alignment.html @@ -0,0 +1,38 @@ +
+

With alignment

+ + + + First Step + Second Step + Third Step + +
+ + + + diff --git a/elements/pf-v5-progress-stepper/demo/compact.html b/elements/pf-v5-progress-stepper/demo/compact.html new file mode 100644 index 0000000000..4145a1f056 --- /dev/null +++ b/elements/pf-v5-progress-stepper/demo/compact.html @@ -0,0 +1,38 @@ +
+

Compact

+ + + + First Step + Second Step + Third Step + +
+ + + + diff --git a/elements/pf-v5-progress-stepper/demo/custom-icons.html b/elements/pf-v5-progress-stepper/demo/custom-icons.html new file mode 100644 index 0000000000..95225c30a8 --- /dev/null +++ b/elements/pf-v5-progress-stepper/demo/custom-icons.html @@ -0,0 +1,18 @@ +
+ + Successful completion + In process + Pending + +
+ + + + diff --git a/elements/pf-v5-progress-stepper/demo/danger.html b/elements/pf-v5-progress-stepper/demo/danger.html new file mode 100644 index 0000000000..a967fe6679 --- /dev/null +++ b/elements/pf-v5-progress-stepper/demo/danger.html @@ -0,0 +1,20 @@ +
+ + First Step + Second Step + Third Step + Fourth Step + Fifth Step + +
+ + + + diff --git a/elements/pf-v5-progress-stepper/demo/index.html b/elements/pf-v5-progress-stepper/demo/index.html new file mode 100644 index 0000000000..b2d3b2f222 --- /dev/null +++ b/elements/pf-v5-progress-stepper/demo/index.html @@ -0,0 +1,18 @@ +
+ + First Step + Second Step + Third Step + +
+ + + + diff --git a/elements/pf-v5-progress-stepper/demo/info.html b/elements/pf-v5-progress-stepper/demo/info.html new file mode 100644 index 0000000000..aa93f3a16b --- /dev/null +++ b/elements/pf-v5-progress-stepper/demo/info.html @@ -0,0 +1,37 @@ +
+ + First Step + Second Step + Third Step + Fourth Step + Fifth Step + +
+ + + + diff --git a/elements/pf-v5-progress-stepper/demo/step-descriptions.html b/elements/pf-v5-progress-stepper/demo/step-descriptions.html new file mode 100644 index 0000000000..b77dec2d55 --- /dev/null +++ b/elements/pf-v5-progress-stepper/demo/step-descriptions.html @@ -0,0 +1,18 @@ +
+ + First Step + Second Step + Third Step + +
+ + + + diff --git a/elements/pf-progress-stepper/docs/CHANGELOG.old.md b/elements/pf-v5-progress-stepper/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-progress-stepper/docs/CHANGELOG.old.md rename to elements/pf-v5-progress-stepper/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-progress-stepper/docs/pf-v5-progress-stepper.md b/elements/pf-v5-progress-stepper/docs/pf-v5-progress-stepper.md new file mode 100644 index 0000000000..4803153f39 --- /dev/null +++ b/elements/pf-v5-progress-stepper/docs/pf-v5-progress-stepper.md @@ -0,0 +1,37 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + + Completed + Issue + Failure + Running + Last + +{% endrenderOverview %} + +{% band header="Usage" %} + {% htmlexample %} + + Completed + Issue + Failure + Running + Last + + {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderSlots for='pf-v5-progress-step', header='Slots on `pf-v5-progress-step`' %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-progress-stepper/docs/screenshot.png b/elements/pf-v5-progress-stepper/docs/screenshot.png similarity index 100% rename from elements/pf-progress-stepper/docs/screenshot.png rename to elements/pf-v5-progress-stepper/docs/screenshot.png diff --git a/elements/pf-v5-progress-stepper/pf-v5-progress-step.css b/elements/pf-v5-progress-stepper/pf-v5-progress-step.css new file mode 100644 index 0000000000..10e2bd49ed --- /dev/null +++ b/elements/pf-v5-progress-stepper/pf-v5-progress-step.css @@ -0,0 +1,124 @@ +[hidden] { + display: none !important; +} + +#icon { + z-index: var(--pf-v5-c-progress-stepper__step-icon--ZIndex); + display: flex; + align-items: center; + justify-content: center; + width: var(--pf-v5-c-progress-stepper__step-icon--Width); + height: var(--pf-v5-c-progress-stepper__step-icon--Height); + color: var(--pf-v5-c-progress-stepper__step-icon--Color); + background-color: var(--pf-v5-c-progress-stepper__step-icon--BackgroundColor); + border: var(--pf-v5-c-progress-stepper__step-icon--BorderWidth) solid var(--pf-v5-c-progress-stepper__step-icon--BorderColor); + border-radius: 50%; + font-size: var(--pf-v5-c-progress-stepper__step-icon--FontSize); + --pf-v5-icon--size: 1.125em; +} + +#main { + position: var(--pf-v5-c-progress-stepper__step-main--Position, initial); + min-width: 0; + margin: var(--pf-v5-c-progress-stepper__step-main--MarginTop) var(--pf-v5-c-progress-stepper__step-main--MarginRight) var(--pf-v5-c-progress-stepper__step-main--MarginBottom) var(--pf-v5-c-progress-stepper__step-main--MarginLeft); + text-align: var(--pf-v5-c-progress-stepper--step-main--TextAlign, auto); + overflow-wrap: anywhere; +} + +:host(:not([current])) #main.compact { + position: fixed; + top: 0; + left: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + margin-bottom: var(--pf-v5-c-progress-stepper--m-compact__step-main--MarginBottom); +} + +:host([current]) #main.compact { + grid-column: 1/-1; + grid-row: 1/2; +} + +#title { + font-size: var(--pf-v5-c-progress-stepper__step-title--FontSize); + font-weight: var(--pf-v5-c-progress-stepper__step-title--FontWeight); + color: var(--pf-v5-c-progress-stepper__step-title--Color); + text-align: var(--pf-v5-c-progress-stepper__step-title--TextAlign); + border: 0; +} + +#description { + margin-top: var(--pf-v5-c-progress-stepper__step-description--MarginTop); + font-size: var(--pf-v5-c-progress-stepper__step-description--FontSize); + color: var(--pf-v5-c-progress-stepper__step-description--Color); + text-align: var(--pf-v5-c-progress-stepper__step-description--TextAlign); + display: block; +} + +:host { + display: contents; +} + +#connector { + position: relative; + display: flex; + justify-content: var(--pf-v5-c-progress-stepper__step-connector--JustifyContent); + width: 100%; +} + +#connector.compact { + min-width: var(--pf-v5-c-progress-stepper--m-compact__step-connector--MinWidth); + grid-row: var(--pf-v5-c-progress-stepper--m-compact__step-connector--GridRow); + padding-bottom: var(--pf-v5-c-progress-stepper--m-compact__step-connector--PaddingBottom); +} + +:host(:not(:last-of-type)) #main::before { + content: var(--pf-v5-c-progress-stepper__step-main--before--Content); + position: absolute; + top: calc(100% + var(--pf-v5-c-progress-stepper__step-main--MarginTop)); + left: calc(50% - var(--pf-v5-c-progress-stepper__step-connector--before--BorderRightWidth) / 2); + width: auto; + height: calc(var(--pf-v5-c-progress-stepper__step-main--MarginTop) + var(--pf-v5-c-progress-stepper__step-main--MarginBottom)); + border-right: var(--pf-v5-c-progress-stepper__step-connector--before--BorderRightWidth) solid var(--pf-v5-c-progress-stepper__step-connector--before--BorderRightColor); +} + +:host(:not(:last-of-type)) #connector::before { + position: absolute; + top: var(--pf-v5-c-progress-stepper__step-connector--before--Top); + left: var(--pf-v5-c-progress-stepper__step-connector--before--Left); + width: var(--pf-v5-c-progress-stepper__step-connector--before--Width); + height: var(--pf-v5-c-progress-stepper__step-connector--before--Height); + content: var(--pf-v5-c-progress-stepper__step-connector--before--Content); + border-right: var(--pf-v5-c-progress-stepper__step-connector--before--BorderRightWidth) solid var(--pf-v5-c-progress-stepper__step-connector--before--BorderRightColor); + border-bottom: var(--pf-v5-c-progress-stepper__step-connector--before--BorderBottomWidth) solid var(--pf-v5-c-progress-stepper__step-connector--before--BorderBottomColor); + transform: var(--pf-v5-c-progress-stepper__step-connector--before--Transform); +} + +:host([current]) { + --pf-v5-c-progress-stepper__step-title--FontWeight: var(--pf-v5-c-progress-stepper__step--m-current__step-title--FontWeight); + --pf-v5-c-progress-stepper__step-title--Color: var(--pf-v5-c-progress-stepper__step--m-current__step-title--Color); +} + +:host([variant="success"]) { + --pf-v5-c-progress-stepper__step-icon--Color: var(--pf-global--success-color--100, #3e8635); +} + +:host([variant="info"]) { + --pf-v5-c-progress-stepper__step-icon--Color: var(--pf-global--info-color--100, #2b9af3); +} + +:host([variant="warning"]) { + --pf-v5-c-progress-stepper__step-icon--Color: var(--pf-global--warning-color--100, #f0ab00); +} + +:host([variant="danger"]) { + --pf-v5-c-progress-stepper__step-icon--Color: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-progress-stepper__step-title--Color: var(--pf-v5-c-progress-stepper__step--m-danger__step-title--Color); + --pf-v5-c-progress-stepper__step-title--m-help-text--hover--Color: var(--pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--Color); + --pf-v5-c-progress-stepper__step-title--m-help-text--focus--Color: var(--pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--Color); + --pf-v5-c-progress-stepper__step-title--m-help-text--TextDecorationColor: var(--pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--TextDecorationColor); + --pf-v5-c-progress-stepper__step-title--m-help-text--hover--TextDecorationColor: var(--pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--TextDecorationColor); + --pf-v5-c-progress-stepper__step-title--m-help-text--focus--TextDecorationColor: var(--pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--TextDecorationColor); +} diff --git a/elements/pf-v5-progress-stepper/pf-v5-progress-step.ts b/elements/pf-v5-progress-stepper/pf-v5-progress-step.ts new file mode 100644 index 0000000000..421e9497d6 --- /dev/null +++ b/elements/pf-v5-progress-stepper/pf-v5-progress-step.ts @@ -0,0 +1,87 @@ +import type { PropertyValues, TemplateResult } from 'lit'; +import type { PfV5ProgressStepper } from './pf-v5-progress-stepper.js'; + +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; + +import style from './pf-v5-progress-step.css'; + +const ICONS = new Map(Object.entries({ + success: { icon: 'check-circle' }, + danger: { icon: 'exclamation-circle' }, + warning: { icon: 'exclamation-triangle' }, + info: { icon: 'resources-full', set: 'patternfly' }, +})); + +/** + * @slot - + * Short description of the current step. + * @slot description + * Longer description of the current step. + * @slot icon + * Overrides the icon property + */ +@customElement('pf-v5-progress-step') +export class PfV5ProgressStep extends LitElement { + protected static parentTagName = 'pf-v5-progress-stepper'; + + static readonly styles: CSSStyleSheet[] = [style]; + + /** Optional extended description of the step */ + @property() description?: string; + + /** Step Icon */ + @property() icon?: string; + + /** Step icon set */ + @property({ attribute: 'icon-set' }) iconSet?: string; + + /** Describes the state of the current item */ + @property({ reflect: true }) variant?: 'pending' | 'info' | 'success' | 'warning' | 'danger'; + + /** Indicates if this item is the current active item. */ + @property({ type: Boolean, reflect: true }) current = false; + + #slots = new SlotController(this, 'title', 'description'); + + #internals = InternalsController.of(this, { role: 'listitem' }); + + render(): TemplateResult<1> { + const hasDescription = !!(this.description ?? this.#slots.hasSlotted('description')); + const icon = this.icon ?? ICONS.get(this.variant ?? 'default')?.icon; + const set = this.iconSet ?? ICONS.get(this.variant ?? 'default')?.set; + const { parentTagName } = (this.constructor as typeof PfV5ProgressStep); + const { compact = false } = this.closest?.(parentTagName) ?? {}; + return html` +
+ + + +
+
+ + ${this.description} +
+ `; + } + + updated(changed: PropertyValues): void { + if (changed.has('current')) { + this.#internals.ariaCurrent = String(!!this.current); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-progress-step': PfV5ProgressStep; + } +} diff --git a/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.css b/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.css new file mode 100644 index 0000000000..08765561a2 --- /dev/null +++ b/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.css @@ -0,0 +1,324 @@ +[hidden] { + display: none !important; +} + +:host { + /** Grid auto flow for vertical layout */ + --pf-v5-c-progress-stepper--m-vertical--GridAutoFlow: row; + /** Grid template columns for vertical layout */ + --pf-v5-c-progress-stepper--m-vertical--GridTemplateColumns: auto 1fr; + /** Top position for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--Top: 0; + /** Left position for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--Left: calc(var(--pf-v5-c-progress-stepper__step-icon--Width) / 2); + /** Width for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--Width: auto; + /** Height for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--Height: 100%; + /** Right border width for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderRightWidth: var(--pf-global--BorderWidth--md, 2px); + /** Right border color for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderRightColor: var(--pf-global--BorderColor--100, #d2d2d2); + /** Bottom border width for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderBottomWidth: 0; + /** Bottom border color for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderBottomColor: transparent; + /** Transform for vertical step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-vertical__step-connector--before--Transform: translateX(-50%); + /** Top margin for vertical step main content */ + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginTop: var(--pf-global--spacer--xs, 0.25rem); + /** Right margin for vertical step main content */ + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginRight: 0; + /** Bottom margin for vertical step main content */ + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginBottom: var(--pf-global--spacer--xl, 2rem); + /** Left margin for vertical step main content */ + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); + /** Grid template columns for vertical compact layout */ + --pf-v5-c-progress-stepper--m-vertical--m-compact--GridTemplateColumns: 1fr; + /** Padding bottom for vertical compact step connector */ + --pf-v5-c-progress-stepper--m-vertical--m-compact__step-connector--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + /** Grid row for vertical compact step connector */ + --pf-v5-c-progress-stepper--m-vertical--m-compact__step-connector--GridRow: auto; + /** Right margin for vertical compact step main content */ + --pf-v5-c-progress-stepper--m-vertical--m-compact__step-main--MarginRight: 0; + /** Right margin for vertical centered step main content */ + --pf-v5-c-progress-stepper--m-vertical--m-center__step-main--MarginRight: 0; + /** Left margin for vertical centered step main content */ + --pf-v5-c-progress-stepper--m-vertical--m-center__step-main--MarginLeft: 0; + /** Grid auto flow for horizontal layout */ + --pf-v5-c-progress-stepper--m-horizontal--GridAutoFlow: column; + /** Grid template columns for horizontal layout */ + --pf-v5-c-progress-stepper--m-horizontal--GridTemplateColumns: initial; + /** Top position for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Top: calc(var(--pf-v5-c-progress-stepper__step-icon--Height) / 2); + /** Left position for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Left: 0; + /** Width for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Width: 100%; + /** Height for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Height: auto; + /** Right border width for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderRightWidth: 0; + /** Right border color for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderRightColor: unset; + /** Bottom border width for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + /** Bottom border color for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomColor: var(--pf-global--BorderColor--100, #d2d2d2); + /** Transform for horizontal step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Transform: translateY(-50%); + /** Top margin for horizontal step main content */ + --pf-v5-c-progress-stepper--m-horizontal__step-main--MarginTop: var(--pf-global--spacer--xs, 0.25rem); + /** Right margin for horizontal step main content */ + --pf-v5-c-progress-stepper--m-horizontal__step-main--MarginRight: var(--pf-global--spacer--xs, 0.25rem); + /** Bottom margin for horizontal step main content */ + --pf-v5-c-progress-stepper--m-horizontal__step-main--MarginBottom: 0; + /** Left margin for horizontal step main content */ + --pf-v5-c-progress-stepper--m-horizontal__step-main--MarginLeft: 0; + /** Grid template columns for horizontal compact layout */ + --pf-v5-c-progress-stepper--m-horizontal--m-compact--GridTemplateColumns: repeat(auto-fill, 1.75rem); + /** Padding bottom for horizontal compact step connector */ + --pf-v5-c-progress-stepper--m-horizontal--m-compact__step-connector--PaddingBottom: 0; + /** Grid row for horizontal compact step connector */ + --pf-v5-c-progress-stepper--m-horizontal--m-compact__step-connector--GridRow: 2; + /** Grid auto flow for compact layout */ + --pf-v5-c-progress-stepper--m-compact--GridAutoFlow: row; + /** Top margin for compact step main content */ + --pf-v5-c-progress-stepper--m-compact__step-main--MarginTop: 0; + /** Bottom margin for compact step main content */ + --pf-v5-c-progress-stepper--m-compact__step-main--MarginBottom: var(--pf-global--spacer--xs, 0.25rem); + /** Minimum width for compact step connector */ + --pf-v5-c-progress-stepper--m-compact__step-connector--MinWidth: 1.75rem; + /** Width for compact step icon */ + --pf-v5-c-progress-stepper--m-compact__step-icon--Width: 1.125rem; + /** Font size for compact step icon */ + --pf-v5-c-progress-stepper--m-compact__step-icon--FontSize: var(--pf-global--icon--FontSize--sm, 0.625rem); + /** Font size for compact step title */ + --pf-v5-c-progress-stepper--m-compact__step-title--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** Font weight for compact step title */ + --pf-v5-c-progress-stepper--m-compact__step-title--FontWeight: var(--pf-global--FontWeight--normal, 400); + /** Top margin for compact pficon */ + --pf-v5-c-progress-stepper--m-compact__pficon--MarginTop: 2px; + /** Top margin for compact exclamation triangle icon */ + --pf-v5-c-progress-stepper--m-compact__fa-exclamation-triangle--MarginTop: -3px; + /** Left position for centered step connector pseudo-element */ + --pf-v5-c-progress-stepper--m-center__step-connector--before--Left: 50%; + /** Grid template columns for centered layout */ + --pf-v5-c-progress-stepper--m-center--GridTemplateColumns: 1fr; + /** Justify content for centered step connector */ + --pf-v5-c-progress-stepper--m-center__step-connector--JustifyContent: center; + /** Right margin for centered step main content */ + --pf-v5-c-progress-stepper--m-center__step-main--MarginRight: var(--pf-global--spacer--xs, 0.25rem); + /** Left margin for centered step main content */ + --pf-v5-c-progress-stepper--m-center__step-main--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); + /** Text alignment for centered step main content */ + --pf-v5-c-progress-stepper--m-center__step-main--TextAlign: center; + /** Right margin for centered step description */ + --pf-v5-c-progress-stepper--m-center__step-description--MarginRight: 0; + /** Left margin for centered step description */ + --pf-v5-c-progress-stepper--m-center__step-description--MarginLeft: 0; + /** Grid template rows */ + --pf-v5-c-progress-stepper--GridTemplateRows: auto 1fr; + /** Justify content for step connector */ + --pf-v5-c-progress-stepper__step-connector--JustifyContent: start; + /** Z-index for step icon */ + --pf-v5-c-progress-stepper__step-icon--ZIndex: var(--pf-global--ZIndex--xs, 100); + /** Width of the step icon */ + --pf-v5-c-progress-stepper__step-icon--Width: 1.75rem; + /** Height of the step icon */ + --pf-v5-c-progress-stepper__step-icon--Height: var(--pf-v5-c-progress-stepper__step-icon--Width); + /** Font size of the step icon */ + --pf-v5-c-progress-stepper__step-icon--FontSize: var(--pf-global--FontSize--md, 1rem); + /** Color of the step icon */ + --pf-v5-c-progress-stepper__step-icon--Color: var(--pf-global--Color--100, #151515); + /** Background color of the step icon */ + --pf-v5-c-progress-stepper__step-icon--BackgroundColor: var(--pf-global--BackgroundColor--light-200, #fafafa); + /** Border width of the step icon */ + --pf-v5-c-progress-stepper__step-icon--BorderWidth: var(--pf-global--BorderWidth--md, 2px); + /** Border color of the step icon */ + --pf-v5-c-progress-stepper__step-icon--BorderColor: var(--pf-global--BorderColor--100, #d2d2d2); + /** Top margin for pficon */ + --pf-v5-c-progress-stepper__pficon--MarginTop: 3px; + /** Top margin for exclamation triangle icon */ + --pf-v5-c-progress-stepper__fa-exclamation-triangle--MarginTop: -5px; + /** Color of the step title */ + --pf-v5-c-progress-stepper__step-title--Color: var(--pf-global--Color--100, #151515); + /** Text alignment of the step title */ + --pf-v5-c-progress-stepper__step-title--TextAlign: left; + /** Font size of the step title */ + --pf-v5-c-progress-stepper__step-title--FontSize: var(--pf-global--FontSize--md, 1rem); + /** Font weight of the step title */ + --pf-v5-c-progress-stepper__step-title--FontWeight: var(--pf-global--FontWeight--normal, 400); + /** Font weight for current step title */ + --pf-v5-c-progress-stepper__step--m-current__step-title--FontWeight: var(--pf-global--FontWeight--bold, 700); + /** Color for current step title */ + --pf-v5-c-progress-stepper__step--m-current__step-title--Color: var(--pf-global--Color--100, #151515); + /** Color for pending step title */ + --pf-v5-c-progress-stepper__step--m-pending__step-title--Color: var(--pf-global--Color--200, #6a6e73); + /** Color for danger step title */ + --pf-v5-c-progress-stepper__step--m-danger__step-title--Color: var(--pf-global--danger-color--100, #c9190b); + /** Text decoration color for help text step title */ + --pf-v5-c-progress-stepper__step-title--m-help-text--TextDecorationColor: var(--pf-global--BorderColor--200, #8a8d90); + /** Text decoration thickness for help text step title */ + --pf-v5-c-progress-stepper__step-title--m-help-text--TextDecorationThickness: var(--pf-global--BorderWidth--sm, 1px); + /** Text underline offset for help text step title */ + --pf-v5-c-progress-stepper__step-title--m-help-text--TextUnderlineOffset: 0.25rem; + /** Text decoration color for help text step title on hover */ + --pf-v5-c-progress-stepper__step-title--m-help-text--hover--TextDecorationColor: var(--pf-global--Color--100, #151515); + /** Text decoration color for help text step title on focus */ + --pf-v5-c-progress-stepper__step-title--m-help-text--focus--TextDecorationColor: var(--pf-global--Color--100, #151515); + /** Color for help text step title on hover */ + --pf-v5-c-progress-stepper__step-title--m-help-text--hover--Color: var(--pf-global--Color--100, #151515); + /** Color for help text step title on focus */ + --pf-v5-c-progress-stepper__step-title--m-help-text--focus--Color: var(--pf-global--Color--100, #151515); + /** Color for danger help text step title on hover */ + --pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--Color: var(--pf-global--danger-color--200, #a30000); + /** Color for danger help text step title on focus */ + --pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--Color: var(--pf-global--danger-color--200, #a30000); + /** Text decoration color for danger help text step title */ + --pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--TextDecorationColor: var(--pf-global--danger-color--100, #c9190b); + /** Text decoration color for danger help text step title on hover */ + --pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--hover--TextDecorationColor: var(--pf-global--danger-color--200, #a30000); + /** Text decoration color for danger help text step title on focus */ + --pf-v5-c-progress-stepper__step--m-danger__step-title--m-help-text--focus--TextDecoerationColor: var(--pf-global--danger-color--200, #a30000); + /** Top margin for step description */ + --pf-v5-c-progress-stepper__step-description--MarginTop: var(--pf-global--spacer--xs, 0.25rem); + /** Font size of the step description */ + --pf-v5-c-progress-stepper__step-description--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** Color of the step description */ + --pf-v5-c-progress-stepper__step-description--Color: var(--pf-global--Color--200, #6a6e73); + /** Text alignment of the step description */ + --pf-v5-c-progress-stepper__step-description--TextAlign: left; + /** Grid auto flow (defaults to vertical) */ + --pf-v5-c-progress-stepper--GridAutoFlow: var(--pf-v5-c-progress-stepper--m-vertical--GridAutoFlow); + /** Grid template columns (defaults to vertical) */ + --pf-v5-c-progress-stepper--GridTemplateColumns: var(--pf-v5-c-progress-stepper--m-vertical--GridTemplateColumns); + /** Top position for step connector pseudo-element (defaults to vertical) */ + --pf-v5-c-progress-stepper__step-connector--before--Top: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Top); + --pf-v5-c-progress-stepper__step-connector--before--Left: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Left); + --pf-v5-c-progress-stepper__step-connector--before--Width: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Width); + --pf-v5-c-progress-stepper__step-connector--before--Height: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Height); + --pf-v5-c-progress-stepper__step-connector--before--BorderRightWidth: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderRightWidth); + --pf-v5-c-progress-stepper__step-connector--before--BorderRightColor: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderRightColor); + --pf-v5-c-progress-stepper__step-connector--before--BorderBottomWidth: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderBottomWidth); + --pf-v5-c-progress-stepper__step-connector--before--BorderBottomColor: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderBottomColor); + --pf-v5-c-progress-stepper__step-connector--before--Transform: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Transform); + --pf-v5-c-progress-stepper__step-main--MarginTop: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginTop); + --pf-v5-c-progress-stepper__step-main--MarginRight: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginRight); + --pf-v5-c-progress-stepper__step-main--MarginBottom: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginBottom); + --pf-v5-c-progress-stepper__step-main--MarginLeft: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginLeft); + --pf-v5-c-progress-stepper--m-compact--GridTemplateColumns: var(--pf-v5-c-progress-stepper--m-vertical--m-compact--GridTemplateColumns); + --pf-v5-c-progress-stepper--m-compact__step-connector--GridRow: var(--pf-v5-c-progress-stepper--m-vertical--m-compact__step-connector--GridRow); + --pf-v5-c-progress-stepper--m-compact__step-connector--PaddingBottom: var(--pf-v5-c-progress-stepper--m-vertical--m-compact__step-connector--PaddingBottom); + --pf-v5-c-progress-stepper--m-center__step-connector--before--Content: none; + --pf-v5-c-progress-stepper--m-center__step-main--before--Content: ''; + --pf-v5-c-progress-stepper__step-connector--before--Content: ''; + --pf-v5-c-progress-stepper__step-main--before--Content: none; + position: relative; + display: grid; + grid-auto-flow: var(--pf-v5-c-progress-stepper--GridAutoFlow); + grid-template-columns: var(--pf-v5-c-progress-stepper--GridTemplateColumns); + grid-template-rows: var(--pf-v5-c-progress-stepper--GridTemplateRows); + grid-auto-columns: 1fr; +} + +@media (min-width: 768px) { + :host { + --pf-v5-c-progress-stepper--GridAutoFlow: var(--pf-v5-c-progress-stepper--m-horizontal--GridAutoFlow, column); + --pf-v5-c-progress-stepper--GridTemplateColumns: var(--pf-v5-c-progress-stepper--m-horizontal--GridTemplateColumns, initial); + --pf-v5-c-progress-stepper__step-connector--before--Top: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Top); + --pf-v5-c-progress-stepper__step-connector--before--Left: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Left); + --pf-v5-c-progress-stepper__step-connector--before--Width: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Width); + --pf-v5-c-progress-stepper__step-connector--before--Height: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Height); + --pf-v5-c-progress-stepper__step-connector--before--BorderRightWidth: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderRightWidth); + --pf-v5-c-progress-stepper__step-connector--before--BorderRightColor: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderRightColor); + --pf-v5-c-progress-stepper__step-connector--before--BorderBottomWidth: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomWidth); + --pf-v5-c-progress-stepper__step-connector--before--BorderBottomColor: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--BorderBottomColor); + --pf-v5-c-progress-stepper__step-connector--before--Transform: var(--pf-v5-c-progress-stepper--m-horizontal__step-connector--before--Transform); + --pf-v5-c-progress-stepper__step-main--MarginTop: var(--pf-v5-c-progress-stepper--m-horizontal__step-main--MarginTop); + --pf-v5-c-progress-stepper__step-main--MarginRight: var(--pf-v5-c-progress-stepper--m-horizontal__step-main--MarginRight); + --pf-v5-c-progress-stepper__step-main--MarginBottom: var(--pf-v5-c-progress-stepper--m-horizontal__step-main--MarginBottom); + --pf-v5-c-progress-stepper__step-main--MarginLeft: var(--pf-v5-c-progress-stepper--m-horizontal__step-main--MarginLeft); + --pf-v5-c-progress-stepper--m-compact--GridTemplateColumns: var(--pf-v5-c-progress-stepper--m-horizontal--m-compact--GridTemplateColumns); + --pf-v5-c-progress-stepper--m-compact__step-connector--GridRow: var(--pf-v5-c-progress-stepper--m-horizontal--m-compact__step-connector--GridRow); + --pf-v5-c-progress-stepper--m-compact__step-connector--PaddingBottom: var(--pf-v5-c-progress-stepper--m-horizontal--m-compact__step-connector--PaddingBottom); + --pf-v5-c-progress-stepper__step-connector--before--Content: var(--pf-v5-c-progress-stepper--m-center__step-connector--before--Content); + --pf-v5-c-progress-stepper__step-main--before--Content: var(--pf-v5-c-progress-stepper--m-center__step-main--before--Content); + --pf-v5-c-progress-stepper--m-center__step-connector--before--Content: ''; + --pf-v5-c-progress-stepper--m-center__step-main--before--Content: none; + } +} + +:host([center]) { + --pf-v5-c-progress-stepper__step-connector--JustifyContent: var(--pf-v5-c-progress-stepper--m-center__step-connector--JustifyContent); + --pf-v5-c-progress-stepper__step-main--MarginRight: var(--pf-v5-c-progress-stepper--m-center__step-main--MarginRight); + --pf-v5-c-progress-stepper__step-main--MarginLeft: var(--pf-v5-c-progress-stepper--m-center__step-main--MarginLeft); + --pf-v5-c-progress-stepper--step-main--TextAlign: var(--pf-v5-c-progress-stepper--m-center__step-main--TextAlign, auto); + --pf-v5-c-progress-stepper__step-title--TextAlign: var(--pf-v5-c-progress-stepper--m-center__step-title--TextAlign, auto); + --pf-v5-c-progress-stepper__step-description--MarginRight: var(--pf-v5-c-progress-stepper--m-center__step-description--MarginRight); + --pf-v5-c-progress-stepper__step-description--MarginLeft: var(--pf-v5-c-progress-stepper--m-center__step-description--MarginLeft); + --pf-v5-c-progress-stepper__step-description--TextAlign: var(--pf-v5-c-progress-stepper--m-center__step-description--TextAlign, auto); + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginRight: var(--pf-v5-c-progress-stepper--m-vertical--m-center__step-main--MarginRight); + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginLeft: var(--pf-v5-c-progress-stepper--m-vertical--m-center__step-main--MarginLeft); + --pf-v5-c-progress-stepper__step-connector--before--Content: var(--pf-v5-c-progress-stepper--m-center__step-connector--before--Content); + --pf-v5-c-progress-stepper__step-main--before--Content: var(--pf-v5-c-progress-stepper--m-center__step-main--before--Content); + --pf-v5-c-progress-stepper__step-connector--before--Left: var(--pf-v5-c-progress-stepper--m-center__step-connector--before--Left); + + grid-template-columns: var(--pf-v5-c-progress-stepper--m-center--GridTemplateColumns); +} + +:host([center]:not([compact])) { + --pf-v5-c-progress-stepper__step-main--before--Content: var(--pf-v5-c-progress-stepper--m-center__step-main--before--Content); + --pf-v5-c-progress-stepper__step-connector--before--Content: var(--pf-v5-c-progress-stepper--m-center__step-connector--before--Content); +} + +:host([vertical]) ::slotted(pf-v5-progress-step) { + flex-direction: row; + align-items: flex-start; +} + +:host([vertical]) { + --pf-v5-c-progress-stepper--GridAutoFlow: var(--pf-v5-c-progress-stepper--m-vertical--GridAutoFlow); + --pf-v5-c-progress-stepper--GridTemplateColumns: var(--pf-v5-c-progress-stepper--m-vertical--GridTemplateColumns); + --pf-v5-c-progress-stepper__step-connector--before--Top: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Top); + --pf-v5-c-progress-stepper__step-connector--before--Left: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Left); + --pf-v5-c-progress-stepper__step-connector--before--Width: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Width); + --pf-v5-c-progress-stepper__step-connector--before--Height: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Height); + --pf-v5-c-progress-stepper__step-connector--before--BorderRightWidth: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderRightWidth); + --pf-v5-c-progress-stepper__step-connector--before--BorderRightColor: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderRightColor); + --pf-v5-c-progress-stepper__step-connector--before--BorderBottomWidth: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderBottomWidth); + --pf-v5-c-progress-stepper__step-connector--before--BorderBottomColor: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--BorderBottomColor); + --pf-v5-c-progress-stepper__step-connector--before--Transform: var(--pf-v5-c-progress-stepper--m-vertical__step-connector--before--Transform); + --pf-v5-c-progress-stepper__step-main--MarginTop: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginTop); + --pf-v5-c-progress-stepper__step-main--MarginRight: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginRight); + --pf-v5-c-progress-stepper__step-main--MarginBottom: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginBottom); + --pf-v5-c-progress-stepper__step-main--MarginLeft: var(--pf-v5-c-progress-stepper--m-vertical__step-main--MarginLeft); + --pf-v5-c-progress-stepper--m-compact--GridTemplateColumns: var(--pf-v5-c-progress-stepper--m-vertical--m-compact--GridTemplateColumns); + --pf-v5-c-progress-stepper--m-compact__step-connector--GridRow: var(--pf-v5-c-progress-stepper--m-vertical--m-compact__step-connector--GridRow); + --pf-v5-c-progress-stepper--m-compact__step-connector--PaddingBottom: var(--pf-v5-c-progress-stepper--m-vertical--m-compact__step-connector--PaddingBottom); + --pf-v5-c-progress-stepper__step-connector--before--Content: ''; + --pf-v5-c-progress-stepper__step-main--before--Content: none; + --pf-v5-c-progress-stepper--m-center__step-connector--before--Content: none; + --pf-v5-c-progress-stepper--m-center__step-main--before--Content: ''; + --pf-v5-c-progress-stepper__step-main--Position: relative; +} + +:host([vertical][center]) { + --pf-v5-c-progress-stepper__step-connector--before--Left: var(--pf-v5-c-progress-stepper--m-center__step-connector--before--Left); +} + +:host([compact]) { + --pf-v5-c-progress-stepper__step-main--MarginTop: var(--pf-v5-c-progress-stepper--m-compact__step-main--MarginTop); + --pf-v5-c-progress-stepper__step-main--MarginBottom: var(--pf-v5-c-progress-stepper--m-compact__step-main--MarginBottom); + --pf-v5-c-progress-stepper__step-icon--Width: var(--pf-v5-c-progress-stepper--m-compact__step-icon--Width); + --pf-v5-c-progress-stepper__step-icon--FontSize: var(--pf-v5-c-progress-stepper--m-compact__step-icon--FontSize); + --pf-v5-c-progress-stepper__step-title--FontSize: var(--pf-v5-c-progress-stepper--m-compact__step-title--FontSize); + --pf-v5-c-progress-stepper__step--m-current__step-title--FontWeight: var(--pf-v5-c-progress-stepper--m-compact__step-title--FontWeight); + --pf-v5-c-progress-stepper__pficon--MarginTop: var(--pf-v5-c-progress-stepper--m-compact__pficon--MarginTop); + --pf-v5-c-progress-stepper__fa-exclamation-triangle--MarginTop: var(--pf-v5-c-progress-stepper--m-compact__fa-exclamation-triangle--MarginTop); + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginLeft: var(--pf-v5-c-progress-stepper--m-vertical--m-compact__step-main--MarginLeft); + --pf-v5-c-progress-stepper--m-vertical__step-main--MarginRight: var(--pf-v5-c-progress-stepper--m-vertical--m-compact__step-main--MarginRight); + display: inline-grid; + grid-template-columns: var(--pf-v5-c-progress-stepper--m-compact--GridTemplateColumns); + grid-auto-flow: var(--pf-v5-c-progress-stepper--m-compact--GridAutoFlow); +} diff --git a/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.ts b/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.ts new file mode 100644 index 0000000000..ff4eb936eb --- /dev/null +++ b/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.ts @@ -0,0 +1,74 @@ +import { LitElement, html, type PropertyValues, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import style from './pf-v5-progress-stepper.css'; + +import { PfV5ProgressStep } from './pf-v5-progress-step.js'; +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; + +import '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; + +/** + * A **progress stepper** displays a timeline of tasks in a workflow and tracks the user's current progress through this workflow. + * @alias Progress Stepper + */ +@customElement('pf-v5-progress-stepper') +export class PfV5ProgressStepper extends LitElement { + protected static childTagName = 'pf-v5-progress-step'; + + static readonly styles: CSSStyleSheet[] = [style]; + + static formAssociated = true; + + /** Whether to use the vertical layout */ + @property({ type: Boolean, reflect: true }) vertical = false; + + /** Whether to use the center alignment */ + @property({ type: Boolean, reflect: true }) center = false; + + /** Whether to use the compact layout */ + @property({ type: Boolean, reflect: true }) compact = false; + + #internals = InternalsController.of(this, { + role: 'progressbar', + ariaValueNow: this.value.toString(), + }); + + #mo = new MutationObserver(() => this.#onMutation()); + + get value(): number { + const { childTagName } = (this.constructor as typeof PfV5ProgressStepper); + const steps = this.querySelectorAll?.(childTagName) ?? []; + const current = this.querySelector?.(`${childTagName}[current]`); + const n = Array.from(steps).indexOf(current as PfV5ProgressStep) + 1; + return (n / steps.length) * 100; + } + + constructor() { + super(); + this.#mo.observe(this, { childList: true }); + } + + #onMutation() { + this.#internals.ariaValueNow = this.value.toString(); + } + + render(): TemplateResult<1> { + // TODO: add label prop + // eslint-disable-next-line lit-a11y/accessible-name + return html`
`; + } + + updated(changed: PropertyValues): void { + if (changed.has('compact')) { + this.querySelectorAll?.('pf-v5-progress-step').forEach(step => step.requestUpdate()); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-progress-stepper': PfV5ProgressStepper; + } +} diff --git a/elements/pf-progress-stepper/test/pf-progress-stepper.e2e.ts b/elements/pf-v5-progress-stepper/test/pf-progress-stepper.e2e.ts similarity index 94% rename from elements/pf-progress-stepper/test/pf-progress-stepper.e2e.ts rename to elements/pf-v5-progress-stepper/test/pf-progress-stepper.e2e.ts index c218f62249..9686044a19 100644 --- a/elements/pf-progress-stepper/test/pf-progress-stepper.e2e.ts +++ b/elements/pf-v5-progress-stepper/test/pf-progress-stepper.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-progress-stepper'; +const tagName = 'pf-v5-progress-stepper'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-progress-stepper/test/pf-progress-stepper.spec.ts b/elements/pf-v5-progress-stepper/test/pf-progress-stepper.spec.ts new file mode 100644 index 0000000000..dec24386de --- /dev/null +++ b/elements/pf-v5-progress-stepper/test/pf-progress-stepper.spec.ts @@ -0,0 +1,21 @@ +import { html, expect } from '@open-wc/testing'; + +import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; + +import { PfV5ProgressStepper } from '@patternfly/elements/pf-v5-progress-stepper/pf-v5-progress-stepper.js'; +import { PfV5ProgressStep } from '../pf-v5-progress-step.js'; + +describe('', function() { + it('imperatively instantiates', function() { + expect(document.createElement('pf-v5-progress-stepper')).to.be.an.instanceof(PfV5ProgressStepper); + expect(document.createElement('pf-v5-progress-step')).to.be.an.instanceof(PfV5ProgressStep); + }); + + it('it should upgrade', async function() { + const el = await createFixture(html``); + expect(el) + .to.be.an.instanceOf(customElements.get('pf-v5-progress-stepper')) + .and + .to.be.an.instanceOf(PfV5ProgressStepper); + }); +}); diff --git a/elements/pf-v5-progress/README.md b/elements/pf-v5-progress/README.md new file mode 100644 index 0000000000..688ff7ecbb --- /dev/null +++ b/elements/pf-v5-progress/README.md @@ -0,0 +1,33 @@ +# Progress + +A progress bar gives the user a visual representation of their completion status of an ongoing process or task. + +Read more about Progress in the [PatternFly Elements Progress documentation][docs]. + +## Installation + +Load `` via CDN: + +```html + +``` + +Or, if you are using [NPM](https://npm.im), install it + +```bash +npm install @patternfly/elements +``` + +Then once installed, import it to your application: + +```js +import '@patternfly/elements/pf-v5-progress/pf-v5-progress.js'; +``` + +## Usage + +```html + +``` + +[docs]: https://patternflyelements.org/components/progress diff --git a/elements/pf-v5-progress/demo/index.html b/elements/pf-v5-progress/demo/index.html new file mode 100644 index 0000000000..6707c5b632 --- /dev/null +++ b/elements/pf-v5-progress/demo/index.html @@ -0,0 +1,5 @@ + + + diff --git a/elements/pf-v5-progress/demo/kitchen-sink.css b/elements/pf-v5-progress/demo/kitchen-sink.css new file mode 100644 index 0000000000..ccd286b44c --- /dev/null +++ b/elements/pf-v5-progress/demo/kitchen-sink.css @@ -0,0 +1,4 @@ +pf-v5-progress { + padding-bottom: 0.25rem; + display: block; +} \ No newline at end of file diff --git a/elements/pf-v5-progress/demo/kitchen-sink.html b/elements/pf-v5-progress/demo/kitchen-sink.html new file mode 100644 index 0000000000..ca3b20d5ab --- /dev/null +++ b/elements/pf-v5-progress/demo/kitchen-sink.html @@ -0,0 +1,141 @@ + + + +

pf-v5-progress

+ +

Default States

+ + + +

Value

+ + +

Description

+ + +

Aria-label

+ + +

Max

+ + +

Min

+ + +

Size (sm, lg)

+ + + +

Measure Location (Inside, Outside, None)

+ + + + +

Variant (Sucess, danger, warning)

+ + + + +

Variant (Success, Danger, Warning) and Size (sm, lg)

+ + + + + + + +

Variant (Success, Danger, Warning) and Measure Location (Inside, Outside, None)

+ + + + + + + + + + +

Variant (Success, Danger, Warning), Size (sm, lg) and Measure Location (Inside, Outside, None)

+ + + + + + + + + + + + + + + + + + + +

Label w/ no description

+ +

Value

+ + + + +

Size (sm, lg)

+ + + + + + + +

Measure Location (Inside, Outside)

+ + + + + + + +

Variant (Sucess, danger, warning)

+ + + + + + + + + + +

Variant (Success, Danger, Warning) and Size (sm, lg)

+ + + + + + + + + + + + + + + + + + + +

Truncated description

+ + + diff --git a/elements/pf-v5-progress/demo/truncated-description.html b/elements/pf-v5-progress/demo/truncated-description.html new file mode 100644 index 0000000000..ab77ce90a0 --- /dev/null +++ b/elements/pf-v5-progress/demo/truncated-description.html @@ -0,0 +1,20 @@ +
+ +
+ + + + diff --git a/elements/pf-v5-progress/docs/pf-v5-progress.md b/elements/pf-v5-progress/docs/pf-v5-progress.md new file mode 100644 index 0000000000..e66c25db5e --- /dev/null +++ b/elements/pf-v5-progress/docs/pf-v5-progress.md @@ -0,0 +1,49 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + +{% endrenderOverview %} + +{% band header="Usage" %} + + ### Success variant + {% htmlexample %} + + {% endhtmlexample %} + + ### Inside measurement + {% htmlexample %} + + {% endhtmlexample %} + + ### Large size + {% htmlexample %} + + {% endhtmlexample %} + + ### Inside measurement + {% htmlexample %} + + {% endhtmlexample %} + + ### Truncated Description + {% htmlexample %} + + {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-v5-progress/pf-v5-progress.css b/elements/pf-v5-progress/pf-v5-progress.css new file mode 100644 index 0000000000..2088aefb98 --- /dev/null +++ b/elements/pf-v5-progress/pf-v5-progress.css @@ -0,0 +1,229 @@ +* { + box-sizing: border-box; +} + +#container { + --_pf-c-progress__bar--before--BackgroundColorWithOpacity: #0066cc33; /* WARNING: not a recognized token value */ + --_pf-c-progress--m-success__bar--BackgroundColorWithOpacity: #3e863533; /* WARNING: not a recognized token value */ + --_pf-c-progress--m-warning__bar--BackgroundColorWithOpacity: #f0ab0033; /* WARNING: not a recognized token value */ + --_pf-c-progress--m-danger__bar--BackgroundColorWithOpacity: #c9190b33; /* WARNING: not a recognized token value */ + + /** Gap between sections of progress bar */ + --pf-v5-c-progress--GridGap: var(--pf-global--spacer--md, 1rem); + /** Color of progress bar */ + --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-global--primary-color--100, #0066cc); + /** Height of progress bar */ + --pf-v5-c-progress__bar--Height: var(--pf-global--spacer--md, 1rem); + /** Background color of progress bar */ + --pf-v5-c-progress__bar--BackgroundColor: var(--pf-global--BackgroundColor--light-100, #ffffff); + /** Color of status icon */ + --pf-v5-c-progress__status-icon--Color: var(--pf-global--Color--100, #151515); + /** Margin left of status icon */ + --pf-v5-c-progress__status-icon--MarginLeft: var(--pf-global--spacer--sm, 0.5rem); + /** Height of progress bar indicator */ + --pf-v5-c-progress__indicator--Height: var(--pf-v5-c-progress__bar--Height); + /** Background color of progress bar indicator */ + --pf-v5-c-progress__indicator--BackgroundColor: var(--pf-v5-c-progress__bar--before--BackgroundColor); + /** Background color of progress bar when variant is success */ + --pf-v5-c-progress--m-success__bar--BackgroundColor: var(--pf-global--success-color--100, #3e8635); + /** Background color of progress bar when variant is warning */ + --pf-v5-c-progress--m-warning__bar--BackgroundColor: var(--pf-global--warning-color--100, #f0ab00); + /** Background color of progress bar when variant is danger */ + --pf-v5-c-progress--m-danger__bar--BackgroundColor: var(--pf-global--danger-color--100, #c9190b); + /** Color of status icon when variant is success */ + --pf-v5-c-progress--m-success__status-icon--Color: var(--pf-global--success-color--100, #3e8635); + /** Color of status icon when variant is warning */ + --pf-v5-c-progress--m-warning__status-icon--Color: var(--pf-global--warning-color--100, #f0ab00); + /** Color of status icon when variant is danger */ + --pf-v5-c-progress--m-danger__status-icon--Color: var(--pf-global--danger-color--100, #c9190b); + /** Color of progress bar measure when variant is success and measure location is inside */ + --pf-v5-c-progress--m-success--m-inside__measure--Color: var(--pf-global--Color--light-100, #ffffff); + /** Font size of progress bar measure when measure location is outside */ + --pf-v5-c-progress--m-outside__measure--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** Height of progress bar when size is small */ + --pf-v5-c-progress--m-sm__bar--Height: var(--pf-global--spacer--sm, 0.5rem); + /** Font size of progress bar description when size is small */ + --pf-v5-c-progress--m-sm__description--FontSize: var(--pf-global--FontSize--sm, 0.875rem); + /** Height of progress bar when size is large */ + --pf-v5-c-progress--m-lg__bar--Height: var(--pf-global--spacer--lg, 1.5rem); + display: grid; + align-items: end; + grid-gap: var(--pf-v5-c-progress--GridGap); + grid-template-columns: 1fr auto; + grid-template-rows: 1fr auto; + width: 100%; +} + +.sm { + --pf-v5-c-progress__bar--Height: var(--pf-v5-c-progress--m-sm__bar--Height); + --pf-v5-c-progress__indicator--Height: var(--pf-v5-c-progress--m-sm__bar--Height); +} + +.sm #description { + font-size: var(--pf-v5-c-progress--m-sm__description--FontSize); +} + +.lg { + --pf-v5-c-progress__bar--Height: var(--pf-v5-c-progress--m-lg__bar--Height); + --pf-v5-c-progress__indicator--Height: var(--pf-v5-c-progress--m-lg__bar--Height); +} + +.outside #description { + grid-column: 1/3; +} + +.outside #status { + grid-column: 2/3; + grid-row: 2/3; + align-self: center; +} + +.outside progress, +.outside span { + display: inline-block; + font-size: var(--pf-v5-c-progress--m-outside__measure--FontSize); + grid-column: 1/2; +} + +.singleline { + grid-template-rows: 1fr; +} + +.singleline #description { + display: none; + visibility: hidden; +} + +.singleline progress, +.singleline span { + grid-row: 1/2; + grid-column: 1/2; +} + +.singleline #status { + grid-row: 1/2; + grid-column: 2/3; +} + +.outside, .singleline { + grid-template-columns: 1fr fit-content(50%); +} + +#container.success { + --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-v5-c-progress--m-success__bar--BackgroundColor); + --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-success__bar--BackgroundColorWithOpacity); + --pf-v5-c-progress__status-icon--Color: var(--pf-v5-c-progress--m-success__status-icon--Color); +} + +#container.warning { + --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-v5-c-progress--m-warning__bar--BackgroundColor); + --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-warning__bar--BackgroundColorWithOpacity); + --pf-v5-c-progress__status-icon--Color: var(--pf-v5-c-progress--m-warning__status-icon--Color); +} + +#container.danger { + --pf-v5-c-progress__bar--before--BackgroundColor: var(--pf-v5-c-progress--m-danger__bar--BackgroundColor); + --_pf-c-progress__bar--before--BackgroundColorWithOpacity: var(--_pf-c-progress--m-danger__bar--BackgroundColorWithOpacity); + --pf-v5-c-progress__status-icon--Color: var(--pf-v5-c-progress--m-danger__status-icon--Color); +} + +#description { + word-break: break-word; + grid-column: 1/2; +} + +.descriptionTruncated #description { + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#status { + grid-column: 2/3; + grid-row: 1/2; + text-align: right; + word-break: break-word; + display: flex; + align-items: center; + justify-content: end; +} + +pf-v5-icon { + margin-left: var(--pf-v5-c-progress__status-icon--MarginLeft); + color: var(--pf-v5-c-progress__status-icon--Color); +} + +progress { + position: relative; + grid-column: 1/3; + grid-row: 2/3; + align-self: center; + height: var(--pf-v5-c-progress__bar--Height); + background-color: var(--pf-v5-c-progress__bar--BackgroundColor); +} + +.indicator { + position: absolute; + top: 0; + left: 0; + height: var(--pf-v5-c-progress__indicator--Height); + background-color: var(--pf-v5-c-progress__indicator--BackgroundColor); +} + +.indicator { + width: 100%; + height: var(--pf-v5-c-progress__bar--Height); + + display: block; +} + +span { + grid-column: 1/3; + grid-row: 2/3; + text-align: center; + color: var(--pf-v5-c-progress--m-success--m-inside__measure--Color); +} + +span::after { + content: attr(data-value); + position: relative; + height: 100%; +} + +progress[value] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + background: var(--_pf-c-progress__bar--before--BackgroundColorWithOpacity); + + width: 100%; + height: var(--pf-v5-c-progress__bar--Height); +} + +progress:not([value]) { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +progress[value]::-webkit-progress-bar { + background: var(--_pf-c-progress__bar--before--BackgroundColorWithOpacity); +} + +progress[value]::-moz-progress-bar { + background: var(--pf-v5-c-progress__bar--before--BackgroundColor); +} + +progress[value]::-webkit-progress-value { + background-size: 100% 100%; + background-image: linear-gradient( + 90deg, + var(--pf-v5-c-progress__bar--before--BackgroundColor) 100%, + var(--pf-v5-c-progress__bar--before--BackgroundColor) 100% + ); +} + +pf-v5-tooltip { + height: 0.01px; +} diff --git a/elements/pf-v5-progress/pf-v5-progress.ts b/elements/pf-v5-progress/pf-v5-progress.ts new file mode 100644 index 0000000000..2791b1601c --- /dev/null +++ b/elements/pf-v5-progress/pf-v5-progress.ts @@ -0,0 +1,136 @@ +import type { PropertyValues, TemplateResult } from 'lit'; +import { LitElement, html } 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 { styleMap } from 'lit/directives/style-map.js'; + +import styles from './pf-v5-progress.css'; + +const ICONS = new Map(Object.entries({ + success: { icon: 'circle-check' }, + danger: { icon: 'circle-xmark' }, + warning: { icon: 'triangle-exclamation' }, +})); + +/** + * A progress bar gives the user a visual representation of their completion status of an ongoing process or task. + * @summary Display completion status of ongoing process or task. + * @alias Progress + */ +@customElement('pf-v5-progress') +export class PfV5Progress extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + #internals = this.attachInternals(); + + /** Represents the value of the progress bar */ + @property({ reflect: true, type: Number }) value = 0; + + /** Description (title) above the progress bar */ + @property() description?: string; + + /** Indicate whether to truncate the string description (title) */ + @property({ + type: Boolean, + reflect: true, + attribute: 'description-truncated', + }) descriptionTruncated = false; + + /** Maximum value for the progress bar */ + @property({ type: Number, reflect: true }) max = 100; + + /** Minimum value for the progress bar */ + @property({ type: Number, reflect: true }) min = 0; + + /** Size of the progress bar (height) */ + @property() size?: 'sm' | 'lg'; + + /** Where the percentage will be displayed with the progress element */ + @property({ attribute: 'measure-location' }) measureLocation?: 'outside' | 'inside' | 'none'; + + /** Variant of the progress bar */ + @property() variant?: 'success' | 'danger' | 'warning'; + + get #calculatedPercentage(): number { + const { value, min, max } = this; + const percentage = Math.round((value - min) / (max - min) * 100); + if (Number.isNaN(percentage) || percentage < 0) { + return 0; + } + return Math.min(percentage, 100); + } + + get #icon() { + return ICONS.get(this.variant ?? '')?.icon; + } + + override willUpdate(changed: PropertyValues): void { + if (changed.has('value') || changed.has('min') || changed.has('max')) { + this.#internals.ariaValueNow = this.#calculatedPercentage.toString(); + } + if (this.#icon) { + import('@patternfly/elements/pf-v5-icon/pf-v5-icon.js'); + } + if (this.descriptionTruncated) { + import('@patternfly/elements/pf-v5-tooltip/pf-v5-tooltip.js'); + } + } + + render(): TemplateResult<1> { + const { size, measureLocation, variant, description, descriptionTruncated } = this; + const icon = this.#icon; + const singleLine = description?.length === 0; + const pct = this.#calculatedPercentage; + const width = `${pct}%`; + + return html` +
+ + + + ${!descriptionTruncated ? '' : html` + + `} + + ${measureLocation === 'none' ? '' : html` + + `} + + + + ${measureLocation !== 'inside' ? '' : html` + + `} +
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-progress': PfV5Progress; + } +} diff --git a/elements/pf-progress/test/pf-progress.e2e.ts b/elements/pf-v5-progress/test/pf-progress.e2e.ts similarity index 95% rename from elements/pf-progress/test/pf-progress.e2e.ts rename to elements/pf-v5-progress/test/pf-progress.e2e.ts index 2da4cdda68..5aa242fb07 100644 --- a/elements/pf-progress/test/pf-progress.e2e.ts +++ b/elements/pf-v5-progress/test/pf-progress.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-progress'; +const tagName = 'pf-v5-progress'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-v5-progress/test/pf-progress.spec.ts b/elements/pf-v5-progress/test/pf-progress.spec.ts new file mode 100644 index 0000000000..bc0b45a15f --- /dev/null +++ b/elements/pf-v5-progress/test/pf-progress.spec.ts @@ -0,0 +1,52 @@ +import { expect, html, fixture } from '@open-wc/testing'; +import { PfV5Progress } from '@patternfly/elements/pf-v5-progress/pf-v5-progress.js'; + +describe('', function() { + let element: PfV5Progress; + + beforeEach(async function() { + element = await fixture(html` + + + `); + }); + + it('should upgrade', async function() { + const klass = customElements.get('pf-v5-progress'); + expect(element).to.be.an.instanceOf(klass).and.to.be.an.instanceOf(PfV5Progress); + }); + + it('should be accessible', async function() { + await expect(element).shadowDom.to.be.accessible(); + }); + + it('should set the correct value on the progress bar', async function() { + const element = await fixture(html` + + + `); + expect(element.value).to.equal(33); + }); + + it('should set the correct title on the progress bar', async function() { + const element = await fixture(html` + + + `); + expect(element.title).to.equal('Progress title'); + }); + + it('should have the correct value with the max value set', async function() { + const max = Math.floor(Math.random() * 100); + const value = Math.floor(Math.random() * (max)); + + const element = await fixture(html` + + + `); + expect(element.value).to.equal(value); + expect(element.max).to.equal(max); + }); +}); diff --git a/elements/pf-v5-search-input/README.md b/elements/pf-v5-search-input/README.md new file mode 100644 index 0000000000..4310350042 --- /dev/null +++ b/elements/pf-v5-search-input/README.md @@ -0,0 +1,15 @@ +# Search Input +A search input lets users type in words to find specific items or information. As they type, it can show matching results to help them quickly find what they are looking for. + +## Usage +A search input consists of a text field where users can type to find specific content or items. Unlike selects or dropdowns, which offer predefined options, a search input lets users enter their own keywords to filter or locate results. It includes a clear (×) button to easily remove the current input, allowing users to start a new search quickly. + +```html + + Alabama + New Jersey + New York + New Mexico + North Carolina + +``` diff --git a/elements/pf-v5-search-input/demo/disabled.html b/elements/pf-v5-search-input/demo/disabled.html new file mode 100644 index 0000000000..6db81b0db3 --- /dev/null +++ b/elements/pf-v5-search-input/demo/disabled.html @@ -0,0 +1,34 @@ +
+
+ + Blue + Green + Magenta + Orange + Purple + Periwinkle + Pink + Red + Yellow + + Search +
+
+ + + + \ No newline at end of file diff --git a/elements/pf-v5-search-input/demo/index.html b/elements/pf-v5-search-input/demo/index.html new file mode 100644 index 0000000000..94abec8be0 --- /dev/null +++ b/elements/pf-v5-search-input/demo/index.html @@ -0,0 +1,42 @@ +
+ + What is Red Hat Enterprise Linux? + How does Red Hat OpenShift work? + Why use Red Hat Ansible for automation? + Where can Red Hat OpenShift be deployed? + When should you use Red Hat Enterprise Linux? + What is Red Hat Satellite? + How does Red Hat integrate with AWS and other clouds? + Why choose Red Hat over other Linux vendors? + Where can I learn Red Hat technologies? + When does support end for RHEL versions? + What are Red Hat certifications? + How do you secure a RHEL server? + Why use OpenShift instead of vanilla Kubernetes? + Where is Red Hat headquartered? + When should you use Red Hat CoreOS? + What is Red Hat Insights? + How do you manage Red Hat subscriptions? + Why is RHEL considered enterprise-grade? + Where can I download RHEL for testing? + When was Red Hat founded? + +
+ + + + \ No newline at end of file diff --git a/elements/pf-v5-search-input/demo/pf-search-input-with-submit.html b/elements/pf-v5-search-input/demo/pf-search-input-with-submit.html new file mode 100644 index 0000000000..563212ff17 --- /dev/null +++ b/elements/pf-v5-search-input/demo/pf-search-input-with-submit.html @@ -0,0 +1,62 @@ +
+
+ + Alabama + New Jersey + New York + New Mexico + North Carolina + Alabama 1 + New Jersey 1 + New York 1 + New Mexico 1 + North Carolina 1 + Alabama 2 + New Jersey 2 + New York 2 + New Mexico 2 + North Carolina 2 + Alabama 3 + New Jersey 3 + New York 3 + New Mexico 3 + North Carolina 3 + + Search +
+
+ + + + + \ No newline at end of file diff --git a/elements/pf-v5-search-input/docs/pf-v5-search-input.md b/elements/pf-v5-search-input/docs/pf-v5-search-input.md new file mode 100644 index 0000000000..9658591695 --- /dev/null +++ b/elements/pf-v5-search-input/docs/pf-v5-search-input.md @@ -0,0 +1,91 @@ +{% renderInstallation %} {% endrenderInstallation %} + + + +{% renderOverview %} + + Blue + Black + Brown + Bronze + Green + Magenta + Orange + Purple + Periwinkle + Pink + Red + Yellow + +{% endrenderOverview %} + +{% band header="Usage" %} + +#### Search Input + +{% htmlexample %} + {% renderFile "./elements/pf-v5-search-input/demo/index.html" %} +{% endhtmlexample %} + +#### Search Input Form +{% htmlexample %} + {% renderFile "./elements/pf-v5-search-input/demo/pf-search-input-with-submit.html" %} +{% endhtmlexample %} + +#### Disabled +{% htmlexample %} + {% renderFile "./elements/pf-v5-search-input/demo/disabled.html" %} +{% endhtmlexample %} + +{% endband %} + +{% band header="Accessibility" %} + +The search input uses the [Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) recommendations from the WAI ARIA [Authoring Best Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg). + +When the dropdown is disabled it follows [WAI ARIA focusability recommendations](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols) for composite widget elements, where dropdown items are still focusable even when the dropdown is disabled. + +#### Toggle and typeahead input + +When focus is on the toggle, the following keyboard interactions apply: + +| Key | Function | +| ---------------------- | -------------------------------------------------------------------------------------- | +| Down Arrow | Opens the listbox and moves focus to the first listbox item. | +| Tab | Moves focus to the close button if visible; otherwise, moves to the next focusable element, then closes the listbox.| +| Shift + Tab | Moves focus out of element onto the previous focusable item and closes listbox. | + +#### Listbox options + +Listbox options use the [APG's Roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) recommendation. When focus is on the listbox, the following keyboard interactions apply: + +| Key | Function | +| ---------------------- | ------------------------------------------------------------------------------------- | +| Enter | Selects the options and closes the listbox. | +| Space | Selects the options and closes the listbox. | +| Tab | Moves focus out of element onto the next focusable options and closes listbox. | +| Shift + Tab | Moves focus to the toggle button and closes listbox. | +| Up Arrow | Moves focus to the previous option, optionally wrapping from the first to the last. | +| Down Arrow | Moves focus to the next option, optionally wrapping from the last to the first. | +| Left Arrow | Returns focus to the combobox without closing the popup and moves the input cursor one character to the left. If the input cursor is on the left-most character, the cursor does not move. | +| Right Arrow | Returns focus to the combobox without closing the popup and moves the input cursor one character to the right. If the input cursor is on the right-most character, the cursor does not move. | +| Escape | Close the listbox that contains focus and return focus to the input. | +| Any letter | Navigates to the next option that starts with the letter. | + +{% endband %} + +{% renderSlots for="pf-v5-search-input", header="Slots on `pf-v5-search-input`" %}{% endrenderSlots %} +{% renderAttributes for="pf-v5-search-input", header="Attributes on `pf-v5-search-input`" %}{% endrenderAttributes %} +{% renderMethods for="pf-v5-search-input", header="Methods on `pf-v5-search-input`" %}{% endrenderMethods %} +{% renderEvents for="pf-v5-search-input", header="Events on `pf-v5-search-input`" %}{% endrenderEvents %} +{% renderCssCustomProperties for="pf-v5-search-input", header="CSS Custom Properties on `pf-v5-search-input`" %}{% endrenderCssCustomProperties %} +{% renderCssParts for="pf-v5-search-input", header="CSS Parts on `pf-v5-search-input`" %}{% endrenderCssParts %} + +{% renderSlots for="pf-v5-option", header="Slots on `pf-v5-option`" %}{% endrenderSlots %} +{% renderAttributes for="pf-v5-option", header="Attributes on `pf-v5-option`" %}{% endrenderAttributes %} +{% renderMethods for="pf-v5-option", header="Methods on `pf-v5-option`" %}{% endrenderMethods %} +{% renderEvents for="pf-v5-option", header="Events on `pf-v5-option`" %}{% endrenderEvents %} +{% renderCssCustomProperties for="pf-v5-option", header="CSS Custom Properties on `pf-v5-option`" %}{% endrenderCssCustomProperties %} +{% renderCssParts for="pf-v5-option", header="CSS Parts on `pf-v5-option`" %}{% endrenderCssParts %} diff --git a/elements/pf-search-input/docs/screenshot.png b/elements/pf-v5-search-input/docs/screenshot.png similarity index 100% rename from elements/pf-search-input/docs/screenshot.png rename to elements/pf-v5-search-input/docs/screenshot.png diff --git a/elements/pf-v5-search-input/pf-v5-search-input.css b/elements/pf-v5-search-input/pf-v5-search-input.css new file mode 100644 index 0000000000..faf2fcbd6f --- /dev/null +++ b/elements/pf-v5-search-input/pf-v5-search-input.css @@ -0,0 +1,308 @@ +:host { + font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); + font-size: var(--pf-global--FontSize--md, 16px); + font-weight: var(--pf-global--FontWeight--normal, 400); + color: var(--pf-global--Color--100, #151515); + --_pf-option-checkboxes-display: none; + --_pf-option-svg-display: block; + --pf-v5-c-search-input__toggle--PaddingTop: var(--pf-global--spacer--form-element, 0.375rem); + --pf-v5-c-search-input__toggle--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-search-input__toggle--PaddingBottom: var(--pf-global--spacer--form-element, 0.375rem); + --pf-v5-c-search-input__toggle--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-search-input__toggle--MinWidth: var(--pf-global--target-size--MinWidth, 44px); + --pf-v5-c-search-input__toggle--FontSize: var(--pf-global--FontSize--md, 1rem); + --pf-v5-c-search-input__toggle--FontWeight: var(--pf-global--FontWeight--normal, 400); + --pf-v5-c-search-input__toggle--LineHeight: var(--pf-global--LineHeight--md, 1.5); + --pf-v5-c-search-input__toggle--BackgroundColor: var(--pf-global--BackgroundColor--100, #fff); + --pf-v5-c-search-input__toggle--before--BorderTopWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-search-input__toggle--before--BorderRightWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-search-input__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-search-input__toggle--before--BorderLeftWidth: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-search-input__toggle--before--BorderWidth: initial; + --pf-v5-c-search-input__toggle--before--BorderTopColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-search-input__toggle--before--BorderRightColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-search-input__toggle--before--BorderBottomColor: var(--pf-global--BorderColor--200, #8a8d90); + --pf-v5-c-search-input__toggle--before--BorderLeftColor: var(--pf-global--BorderColor--300, #f0f0f0); + --pf-v5-c-search-input__toggle--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-search-input__toggle--hover--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-search-input__toggle--focus--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-search-input__toggle--focus--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-search-input__toggle--active--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-search-input__toggle--active--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-search-input__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--active-color--100, #06c); + --pf-v5-c-search-input__toggle--m-expanded--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-search-input__toggle--disabled--BackgroundColor: var(--pf-global--disabled-color--300, #f0f0f0); + --pf-v5-c-search-input__toggle--m-plain--before--BorderColor: transparent; + --pf-v5-c-search-input__toggle--m-placeholder--Color: transparent; + --pf-v5-c-search-input--m-invalid__toggle--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-search-input--m-invalid__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-search-input--m-invalid__toggle--hover--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-search-input--m-invalid__toggle--focus--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-search-input--m-invalid__toggle--active--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-search-input--m-invalid__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-search-input--m-invalid__toggle-status-icon--Color: var(--pf-global--danger-color--100, #c9190b); + --pf-v5-c-search-input--m-success__toggle--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); + --pf-v5-c-search-input--m-success__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-search-input--m-success__toggle--hover--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); + --pf-v5-c-search-input--m-success__toggle--focus--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); + --pf-v5-c-search-input--m-success__toggle--active--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); + --pf-v5-c-search-input--m-success__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--success-color--100, #3e8635); + --pf-v5-c-search-input--m-success__toggle-status-icon--Color: var(--pf-global--success-color--100, #3e8635); + --pf-v5-c-search-input--m-warning__toggle--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); + --pf-v5-c-search-input--m-warning__toggle--before--BorderBottomWidth: var(--pf-global--BorderWidth--md, 2px); + --pf-v5-c-search-input--m-warning__toggle--hover--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); + --pf-v5-c-search-input--m-warning__toggle--focus--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); + --pf-v5-c-search-input--m-warning__toggle--active--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); + --pf-v5-c-search-input--m-warning__toggle--m-expanded--before--BorderBottomColor: var(--pf-global--warning-color--100, #f0ab00); + --pf-v5-c-search-input--m-warning__toggle-status-icon--Color: var(--pf-global--warning-color--100, #f0ab00); + --pf-v5-c-search-input__toggle-wrapper--not-last-child--MarginRight: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-search-input__toggle-wrapper--MaxWidth: calc(100% - var(--pf-global--spacer--lg, 1.5rem)); + --pf-v5-c-search-input__toggle--m-placeholder__toggle-text--Color: var(--pf-global--Color--dark-200, #6a6e73); + --pf-v5-c-search-input__toggle-icon--toggle-text--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-search-input__toggle-status-icon--MarginLeft: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-search-input__toggle-status-icon--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-search-input--m-plain__toggle-arrow--Color: var(--pf-global--Color--200, #6a6e73); + --pf-v5-c-search-input--m-plain--hover__toggle-arrow--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-search-input__toggle-clear--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-search-input__toggle-clear--PaddingLeft: var(--pf-global--spacer--md, 1rem); + --pf-v5-c-search-input__toggle-clear--toggle-button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-search-input__toggle-button--Color: var(--pf-global--Color--100, #151515); + --pf-v5-c-search-input__list-item--m-loading--PaddingTop: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-search-input__menu-content--MaxHeight: 20rem; +} + +:host, +#outer { + display: flex; + flex-direction: column; + align-items: stretch; + inline-size: 100%; +} + +:host([hidden]), +*[hidden] { + display: none !important; +} + +:host([aria-disabled="true"]) { + pointer-events: none; + cursor: not-allowed; +} + +#outer.disabled { + color: var(--pf-global--Color--dark-200, #6a6e73); +} + +#outer { + position: relative; +} + +#listbox-container { + display: inline-flex; + position: absolute; + background-color: var(--pf-v5-theme--color--surface--lightest, #fff) !important; + opacity: 0; + --_active-descendant-color: var(--pf-v5-theme--color--surface--lighter, #f0f0f0) !important; + box-shadow: 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), 0 0 0.25rem 0 rgba(3, 3, 3, 0.06); +} + +#outer.expanded #listbox-container { + opacity: 1; + z-index: 9999 !important; + max-block-size: var(--pf-v5-c-search-input__menu-content--MaxHeight, 20rem); + overflow-y: scroll; +} + +#listbox { + display: flex; + flex-direction: column; + position: relative; + inline-size: 100%; +} + +#listbox slot.disabled { + color: var(--pf-v5-c-list__item-icon--Color, #6a6e73) !important; + background-color: var(--pf-v5-theme--color--surface--lighter, #f0f0f0) !important; + border-color: var(--pf-v5-theme--color--surface--lighter, #f0f0f0) !important; + pointer-events: none; + cursor: not-allowed; + + --_active-descendant-color: transparent; + --_svg-color: var(--pf-v5-c-list__item-icon--Color, #6a6e73) !important; +} + +#toggle { + background-color: var(--pf-v5-c-search-input__toggle--BackgroundColor, #fff) !important; +} + +#toggle, +#toggle-input { + display: flex; + font-family: var(--pf-global--FontFamily--sans-serif, "RedHatTextUpdated", "Overpass", overpass, helvetica, arial, sans-serif); + font-size: var(--pf-v5-c-search-input__toggle--FontSize, 1rem); + font-weight: var(--pf-v5-c-search-input__toggle--FontWeight, 400); + line-height: var(--pf-v5-c-search-input__toggle--LineHeight, 1.5); +} + +#toggle { + border: 1px solid var(--pf-global--BorderColor--100, #d2d2d2); + border-bottom-color: var(--pf-v5-theme--color--text, #151515); + justify-content: space-between; +} + +.disabled #toggle { + color: var(--pf-global--Color--dark-200, #6a6e73) !important; + background-color: var(--pf-v5-theme--color--surface--lighter, #f0f0f0) !important; + border-color: var(--pf-v5-theme--color--surface--lighter, #f0f0f0) !important; + caret-color: transparent; +} + +#toggle-input { + background: transparent; + border: none; + text-align: left; + border-radius: 0; + padding-inline-start: 3rem; +} + +#toggle-input { + justify-content: space-between; + inline-size: 100%; + box-sizing: border-box; + block-size: 2.25rem; +} + +.disabled #toggle-input { + pointer-events: none; + cursor: not-allowed; +} + +.close-button { + --pf-v5-c-button--PaddingLeft: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-button--PaddingRight: var(--pf-global--spacer--sm, 0.5rem); + --pf-v5-c-button--PaddingTop: var(--pf-global--spacer--xs, 0.25rem); + --pf-v5-c-button--PaddingBottom: var(--pf-global--spacer--xs, 0.25rem); + + color: currentColor; + background-color: transparent; + max-block-size: 2.25rem; + max-inline-size: 2.25rem; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0px; + position: relative; + + pf-v5-icon { + position: relative; + inset-block-start: 5px; + } +} + +.close-button-container { + block-size: 2.25rem; + inline-size: 2.25rem; +} + +#toggle-text { + flex: 1 1 auto; +} + +#description { + display: block; +} + +#listbox.checkboxes { + --_pf-option-checkboxes-display: none; + --_pf-option-svg-display: none; +} + +.visually-hidden { + border: 0; + clip: rect(0, 0, 0, 0); + block-size: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + inline-size: 1px; +} + +::slotted(hr) { + --pf-v5-c-divider--BorderWidth--base: var(--pf-global--BorderWidth--sm, 1px); + --pf-v5-c-divider--BorderColor--base: var(--pf-v5-c-divider--BackgroundColor); + --pf-v5-c-divider--Height: var(--pf-v5-c-divider--BorderWidth--base); + --pf-v5-c-divider--BackgroundColor: var(--pf-global--BorderColor--100, #d2d2d2); + --pf-v5-c-divider--after--BackgroundColor: var(--pf-v5-c-divider--BorderColor--base); + --pf-v5-c-divider--after--FlexBasis: 100%; + --pf-v5-c-divider--after--Inset: 0%; + --pf-v5-c-divider--m-vertical--after--FlexBasis: 100%; + --pf-v5-c-divider--m-horizontal--Display: flex; + --pf-v5-c-divider--m-horizontal--FlexDirection: row; + --pf-v5-c-divider--m-horizontal--after--Height: var(--pf-v5-c-divider--Height); + --pf-v5-c-divider--m-horizontal--after--Width: auto; + --pf-v5-c-divider--m-vertical--Display: inline-flex; + --pf-v5-c-divider--m-vertical--FlexDirection: column; + --pf-v5-c-divider--m-vertical--after--Height: auto; + --pf-v5-c-divider--m-vertical--after--Width: var(--pf-v5-c-divider--BorderWidth--base); + --pf-v5-hidden-visible--visible--Display: var(--pf-v5-c-divider--Display); + --pf-v5-c-divider--Display: var(--pf-v5-c-divider--m-horizontal--Display); + --pf-v5-c-divider--FlexDirection: var(--pf-v5-c-divider--m-horizontal--FlexDirection); + --pf-v5-c-divider--after--Width: var(--pf-v5-c-divider--m-horizontal--after--Width); + --pf-v5-c-divider--after--Height: var(--pf-v5-c-divider--m-horizontal--after--Height); + display: var(--pf-v5-c-divider--Display, flex); + flex-direction: var(--pf-v5-c-divider--FlexDirection); + border: 0; + inline-size: 100%; + margin-top: var(--pf-v5-c-search-input-menu--c-divider--MarginTop); + margin-bottom: var(--pf-v5-c-search-input-menu--c-divider--MarginBottom); +} + +::slotted(hr)::after { + content: ''; + inline-size: var(--pf-v5-c-divider--after--Width, 100%) !important; + block-size: var(--pf-v5-c-divider--after--Height, 1px); + background-color: var(--pf-v5-c-divider--after--BackgroundColor); + flex: 1 0 100%; +} + +div.search-icon { + position: absolute; + inset-block-start: 50%; + inset-inline-start: var(--pf-global--spacer--md, 1rem); + transform: translateY(-50%); + display: flex; + align-items: center; +} + +#outer:focus-within { + #toggle { + border-bottom: none; + border-bottom-left-radius: 4px; + + #toggle-input { + border-bottom: var(--pf-global--spacer--xs, 0.125rem) solid var(--pf-v5-theme--color--accent, #0066cc); + } + } + + .close-button-container { + position: relative; + + &::after { + content: ''; + inline-size: 36px; + block-size: var(--pf-global--spacer--xs, 0.125rem); + inset-block-end: 0px; + inset-inline-start: 0px; + background-color: var(--pf-v5-theme--color--accent, #0066cc); + position: absolute; + } + } +} + +::slotted(pf-v5-option[selected]) { + --_pf-option-svg-display: none; + --_pf-option-selected-background-color: var(--pf-global--BackgroundColor--100, #fff); +} \ No newline at end of file diff --git a/elements/pf-v5-search-input/pf-v5-search-input.ts b/elements/pf-v5-search-input/pf-v5-search-input.ts new file mode 100644 index 0000000000..3252d0ee8b --- /dev/null +++ b/elements/pf-v5-search-input/pf-v5-search-input.ts @@ -0,0 +1,339 @@ +import type { Placement } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; +import type { TemplateResult } from 'lit'; + +import { LitElement, html, isServer } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { query } from 'lit/decorators/query.js'; +import { styleMap } from 'lit/directives/style-map.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { ComboboxController } from '@patternfly/pfe-core/controllers/combobox-controller.js'; +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; +import { FloatingDOMController } from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; + +import { observes } from '@patternfly/pfe-core/decorators/observes.js'; +import { PfV5Option } from '../pf-v5-select/pf-v5-option.js'; +import styles from './pf-v5-search-input.css'; + +/** Fired when a `` element's value changes */ +export class PfV5SearchChangeEvent extends Event { + constructor() { + super('change', { bubbles: true }); + } +} + +/** + * A search input lets users type in words to find specific items or information. + * As they type, it can show matching results to help them quickly find what they are looking for. + * + * A search input consists of a text field where users can type to find specific content or items. + * Unlike selects or dropdowns, which offer predefined options, a search input lets users enter + * their own keywords to filter or locate results. It includes a clear (×) button to easily + * remove the current input, allowing users to start a new search quickly. + * + * @summary Allows users to search through a list for specific search terms + * @alias Search Input + * + * @fires open - when the menu toggles open + * @fires close - when the menu toggles closed + */ +@customElement('pf-v5-search-input') +export class PfV5SearchInput extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + static readonly formAssociated = true; + + static override readonly shadowRootOptions: ShadowRootInit = { + ...LitElement.shadowRootOptions, + delegatesFocus: true, + }; + + /** Accessible label for the search input */ + @property({ attribute: 'accessible-label' }) accessibleLabel?: string; + + /** Whether the search input is disabled */ + @property({ type: Boolean, reflect: true }) disabled = false; + + /** Whether the search input's listbox is expanded */ + @property({ type: Boolean, reflect: true }) expanded = false; + + /** Current form value */ + @property() value?: string; + + /** Placeholder entry. Overridden by the `placeholder` slot */ + @property() placeholder?: string; + + /** + * Indicates initial popover position. + * There are 6 options: `bottom`, `top`, `top-start`, `top-end`, `bottom-start`, `bottom-end`. + * Default is `bottom`. + */ + @property({ reflect: true }) position: Placement = 'bottom'; + + @query('#toggle-input') private _toggleInput?: HTMLInputElement; + + @query('#toggle-button') private _toggleButton?: HTMLDivElement; + + @query('#listbox') private _listbox?: HTMLElement; + + @query('#listbox-container') private _listboxContainer?: HTMLElement; + + @query('#placeholder') private _placeholder?: PfV5Option; + + #internals = InternalsController.of(this); + + #float = new FloatingDOMController(this, { content: () => this._listboxContainer }); + + #slots = new SlotController(this, null, 'placeholder'); + + /** True when the user just clicked the close button */ + #clickedCloseButton = false; + #setExpanded = false; + + #combobox = ComboboxController.of(this, { + getItems: () => this.options, + getFallbackLabel: () => this.accessibleLabel + || this.#internals.computedLabelText + || this.placeholder + || this.#slots.getSlotted('placeholder').map(x => x.textContent).join(''), + getListboxElement: () => this._listbox ?? null, + getToggleButton: () => this._toggleButton ?? null, + getComboboxInput: () => this._toggleInput ?? null, + isExpanded: () => this.#setIsExpanded(), + requestShowListbox: () => this.#showListbox(), + requestHideListbox: () => void (this.expanded &&= false), + setItemHidden: (item, hidden) => (item.id !== 'placeholder') && void (item.hidden = hidden), + isItem: item => item instanceof PfV5Option, + setItemActive: (item, active) => this.#setItemActive(item, active), + setItemSelected: (item, selected) => this.#setItemSelected(item, selected), + }); + + connectedCallback(): void { + super.connectedCallback(); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + } + + /** List of options */ + get options(): PfV5Option[] { + if (isServer) { + return []; // TODO: expose a DOM property to allow setting options in SSR scenarios + } else { + return [ + this._placeholder, + ...Array.from(this.querySelectorAll('pf-v5-option')), + ].filter((x): x is PfV5Option => !!x && !x.hidden); + } + } + + override render(): TemplateResult<1> { + const { disabled, expanded, placeholder } = this; + const { anchor = 'bottom', alignment = 'start', styles = {} } = this.#float; + const { height, width } = this.getBoundingClientRect?.() || {}; + + return html` +
+
+
+ search +
+ +
+ + close + +
+
+
+
+ ${this.#combobox.renderItemsToShadowRoot()} + + +
+
+
+ `; + } + + @observes('disabled') + private disabledChanged() { + this.#combobox.disabled = this.disabled; + } + + @observes('expanded') + private async expandedChanged(old: boolean, expanded: boolean) { + if (this.dispatchEvent(new Event(this.expanded ? 'close' : 'open'))) { + if (expanded) { + this.#doExpand(); + } else { + this.#doCollapse(); + } + } + } + + @observes('value') + private valueChanged() { + this.#internals.setFormValue(this.value ?? ''); + this.dispatchEvent(new PfV5SearchChangeEvent()); + } + + async #doExpand() { + try { + await this.#float.show({ placement: this.position || 'bottom', flip: true }); + return true; + } catch { + return false; + } + } + + async #doCollapse() { + try { + await this.#float.hide(); + return true; + } catch { + return false; + } + } + + /** + * Opens the dropdown + */ + async show(): Promise { + this.expanded = true; + await this.updateComplete; + } + + /** + * Closes listbox + */ + async hide(): Promise { + this.expanded = false; + await this.updateComplete; + } + + /** + * toggles popup based on current state + */ + async toggle(): Promise { + if (this.expanded) { + await this.hide(); + } else { + await this.show(); + } + } + + #onClickCloseButton() { + this._toggleInput!.value = ''; + this.#updateValue(this._toggleInput?.value ?? ''); + this.#clickedCloseButton = true; + this._toggleInput?.focus(); + } + + #hideCloseButton() { + if (!isServer) { + return !this.expanded && this._toggleInput?.value.trim() === ''; // SSR or server-side environment: don't hide the element + } + return false; + } + + #onChange() { + this.#updateValue(this._toggleInput?.value ?? ''); + } + + #onSubmit(event: KeyboardEvent) { + if (event.key === 'Enter' || event.key === ' ') { + this.dispatchEvent(new PfV5SearchChangeEvent()); + } + } + + #updateValue(value: string) { + this.value = value; + // it's necessary to reset the 'selected' state of combobox + // since otherwise, combobox controller will attempt to prevent us from + // re-selecting the last-selected item, even though pf-v5-search-input + // doesn't have a selected property + this.#combobox.selected = []; + } + + #onKeyDown(event: KeyboardEvent) { + const target = event.target as HTMLElement; + if (target?.getAttribute('aria-disabled') === 'true') { + // Allow Tab and Shift+Tab to move focus + if (event.key === 'Tab') { + return; + } + event.preventDefault(); + event.stopImmediatePropagation(); + } + } + + async #showListbox() { + await new Promise(requestAnimationFrame); + if (this.disabled) { + return; + }; + + if (this.#setExpanded) { + // If expanded is set to true on clicking close button + // set expanded to false + this.#setExpanded = false; + this.expanded = false; + } else { + this.expanded ||= true; + } + } + + #setItemSelected(item: PfV5Option, selected: boolean) { + item.selected = selected; + if (selected) { + this._toggleInput!.value = item.value; + this.#updateValue(this._toggleInput?.value ?? ''); + } + } + + #setItemActive(item: PfV5Option, active: boolean) { + item.active = active; + if (this.expanded && active) { + item?.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); + } + } + + #setIsExpanded() { + if (this.#clickedCloseButton) { + this.#clickedCloseButton = false; + this.#setExpanded = true; + return true; + } + return this.expanded; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-search-input': PfV5SearchInput; + } +} diff --git a/elements/pf-search-input/test/pf-search-input.e2e.ts b/elements/pf-v5-search-input/test/pf-search-input.e2e.ts similarity index 94% rename from elements/pf-search-input/test/pf-search-input.e2e.ts rename to elements/pf-v5-search-input/test/pf-search-input.e2e.ts index b1d9dbb21a..04d688a4e4 100644 --- a/elements/pf-search-input/test/pf-search-input.e2e.ts +++ b/elements/pf-v5-search-input/test/pf-search-input.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-search-input'; +const tagName = 'pf-v5-search-input'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-search-input/test/pf-search-input.spec.ts b/elements/pf-v5-search-input/test/pf-search-input.spec.ts similarity index 86% rename from elements/pf-search-input/test/pf-search-input.spec.ts rename to elements/pf-v5-search-input/test/pf-search-input.spec.ts index 47089be493..6317a2e001 100644 --- a/elements/pf-search-input/test/pf-search-input.spec.ts +++ b/elements/pf-v5-search-input/test/pf-search-input.spec.ts @@ -1,6 +1,6 @@ import { aTimeout, expect, html, nextFrame } from '@open-wc/testing'; import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js'; -import { PfSearchInput } from '../pf-search-input.js'; +import { PfV5SearchInput } from '../pf-v5-search-input.js'; import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; import { sendKeys } from '@web/test-runner-commands'; import { clickElementAtCenter, clickElementAtOffset } from '@patternfly/pfe-tools/test/utils.js'; @@ -12,22 +12,22 @@ function press(key: string) { } // a11yShapshot does not surface the options -function getVisibleOptionValues(element: PfSearchInput): string[] { +function getVisibleOptionValues(element: PfV5SearchInput): string[] { return element.options.filter(x => !x.hidden).map(x => x.value); } // a11yShapshot does not surface the options -function getActiveOption(element: PfSearchInput) { +function getActiveOption(element: PfV5SearchInput) { return element.options.find(x => x.active); } /** * NOTE because of the copy-to-shadow-root shtick in ActivedescendantController, * we can't just pick an option (from light dom); - * @param element pf-select + * @param element pf-v5-select * @param index item index */ -async function clickItemAtIndex(element: PfSearchInput, index: number) { +async function clickItemAtIndex(element: PfV5SearchInput, index: number) { const itemHeight = 44; await clickElementAtOffset(element, [ 10, @@ -38,34 +38,34 @@ async function clickItemAtIndex(element: PfSearchInput, index: number) { } -describe('', function() { +describe('', function() { describe('simply instantiating', function() { - let element: PfSearchInput; + let element: PfV5SearchInput; it('imperatively instantiates', function() { - expect(document.createElement('pf-search-input')).to.be.an.instanceof(PfSearchInput); + expect(document.createElement('pf-v5-search-input')).to.be.an.instanceof(PfV5SearchInput); }); it('should upgrade', async function() { - element = await createFixture(html``); - const klass = customElements.get('pf-search-input'); + element = await createFixture(html``); + const klass = customElements.get('pf-v5-search-input'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfSearchInput); + .to.be.an.instanceOf(PfV5SearchInput); }); }); describe('with accessible-label attribute and 3 items', function() { - let element: PfSearchInput; + let element: PfV5SearchInput; const updateComplete = () => element.updateComplete; beforeEach(async function() { - element = await createFixture(html` - - 1 - 2 - 3 - `); + element = await createFixture(html` + + 1 + 2 + 3 + `); }); it('passes aXe audit', async function() { @@ -114,16 +114,16 @@ describe('', function() { }); describe('with 3 items and associated

+ + diff --git a/elements/pf-tooltip/demo/slotted-content.html b/elements/pf-v5-tooltip/demo/slotted-content.html similarity index 77% rename from elements/pf-tooltip/demo/slotted-content.html rename to elements/pf-v5-tooltip/demo/slotted-content.html index 050e09c216..220cfce0af 100644 --- a/elements/pf-tooltip/demo/slotted-content.html +++ b/elements/pf-v5-tooltip/demo/slotted-content.html @@ -1,26 +1,26 @@

Toggle Container Width

- +

A tooltip may contain - + HTML content Slotted content can be formatted, using HTML tags like em, strong, or code. - by using the content slot. + by using the content slot. Slotted content must be phrasing content, so no <p>s.

+ + diff --git a/elements/pf-tooltip/docs/CHANGELOG.old.md b/elements/pf-v5-tooltip/docs/CHANGELOG.old.md similarity index 100% rename from elements/pf-tooltip/docs/CHANGELOG.old.md rename to elements/pf-v5-tooltip/docs/CHANGELOG.old.md diff --git a/elements/pf-v5-tooltip/docs/pf-v5-tooltip.md b/elements/pf-v5-tooltip/docs/pf-v5-tooltip.md new file mode 100644 index 0000000000..1be3099d3d --- /dev/null +++ b/elements/pf-v5-tooltip/docs/pf-v5-tooltip.md @@ -0,0 +1,144 @@ +{% renderInstallation %} {% endrenderInstallation %} + +{% renderOverview %} + ### Default + Tooltip +
+ This is some content +
+
+{% endrenderOverview %} + +{% band header="Usage" %} + ### Left Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Left-Start Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Left-End Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Right Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Right-Start Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Right-End Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Top Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Top-Start Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Top-End Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Bottom Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Bottom-Start Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} + + ### Bottom-End Tooltip + {% htmlexample %} + + Tooltip +
+ This is some content +
+
+ {% endhtmlexample %} +{% endband %} + +{% renderSlots %}{% endrenderSlots %} + +{% renderAttributes %}{% endrenderAttributes %} + +{% renderMethods %}{% endrenderMethods %} + +{% renderEvents %}{% endrenderEvents %} + +{% renderCssCustomProperties %}{% endrenderCssCustomProperties %} + +{% renderCssParts %}{% endrenderCssParts %} diff --git a/elements/pf-tooltip/docs/screenshot.png b/elements/pf-v5-tooltip/docs/screenshot.png similarity index 100% rename from elements/pf-tooltip/docs/screenshot.png rename to elements/pf-v5-tooltip/docs/screenshot.png diff --git a/elements/pf-v5-tooltip/pf-v5-tooltip.css b/elements/pf-v5-tooltip/pf-v5-tooltip.css new file mode 100644 index 0000000000..7fe60ddfdc --- /dev/null +++ b/elements/pf-v5-tooltip/pf-v5-tooltip.css @@ -0,0 +1,108 @@ +:host { + --_timestamp-text-decoration: underline dashed 1px; + --_timestamp-text-underline-offset: 4px; + display: inline; +} + +* { box-sizing: border-box; } + +#container { + display: inline-flex; + position: relative; + max-width: 100%; + /** Tooltip arrow width */ + --_floating-arrow-size: var(--pf-v5-c-tooltip__arrow--Width, 0.5rem); +} + +#tooltip, +#tooltip::after { + position: absolute; +} + +#tooltip { + --_timestamp-text-decoration: none; + --_timestamp-text-underline-offset: initial; + user-select: none; + display: block; + opacity: 0; + pointer-events: none; + z-index: 10000; + transition: opacity 300ms cubic-bezier(0.54, 1.5, 0.38, 1.11) 0s; + text-align: center; + word-break: break-word; + translate: var(--_floating-content-translate); + max-width: calc(100vw - 10px); + width: max-content; + top: 0; + left: 0; + will-change: opacity; + /** Sets the font color for the tooltip content */ + line-height: var(--pf-v5-c-tooltip--line-height, 1.5); + /** Maximum width for the tooltip */ + max-width: var(--pf-v5-c-tooltip--MaxWidth, 18.75rem); + /** Box shadow for the tooltip */ + box-shadow: var(--pf-v5-c-tooltip--BoxShadow, + var(--pf-global--BoxShadow--md, + 0 0.25rem 0.5rem 0rem rgba(3, 3, 3, 0.12), + 0 0 0.25rem 0 rgba(3, 3, 3, 0.06))); + /** Tooltip padding (top, right, bottom, left) */ + padding: + var(--pf-v5-c-tooltip__content--PaddingTop, + var(--pf-global--spacer--sm, 0.5rem)) + var(--pf-v5-c-tooltip__content--PaddingRight, + var(--pf-global--spacer--sm, 0.5rem)) + var(--pf-v5-c-tooltip__content--PaddingBottom, + var(--pf-global--spacer--sm, 0.5rem)) + var(--pf-v5-c-tooltip__content--PaddingLeft, + var(--pf-global--spacer--sm, 0.5rem)); + /** Font size for the tooltip content */ + font-size: var(--pf-v5-c-tooltip__content--FontSize, + var(--pf-global--FontSize--sm, 0.875rem)); + /** Sets the font color for the tooltip content */ + color: var(--pf-v5-c-tooltip__content--Color, + var(--pf-global--Color--light-100, #ffffff)); + /** Sets the background color for the tooltip content */ + background-color: var(--pf-v5-c-tooltip__content--BackgroundColor, + var(--pf-global--BackgroundColor--dark-100, #151515)); +} + +#tooltip::after { + display: block; + content: ''; + rotate: 45deg; + width: var(--_floating-arrow-size); + height: var(--_floating-arrow-size); + will-change: left top right bottom; + background-color: var(--pf-v5-c-tooltip__content--BackgroundColor, + var(--pf-global--BackgroundColor--dark-100, #151515)); +} + +.open #tooltip { + opacity: 1; + user-select: initial; +} + +/* LEFT */ +.left #tooltip::after { right: calc(-0.5 * var(--_floating-arrow-size)); } +.left.center #tooltip::after { top: calc(50% - 0.5 * var(--_floating-arrow-size)); } +.left.start #tooltip::after { top: var(--_floating-arrow-size); } +.left.end #tooltip::after { bottom: var(--_floating-arrow-size); } + +/* TOP */ +.top #tooltip::after { top: calc(100% - 0.5 * var(--_floating-arrow-size)); } +.top.center #tooltip::after { right: calc(50% - 0.5 * var(--_floating-arrow-size)); } +.top.start #tooltip::after { left: var(--_floating-arrow-size); } +.top.end #tooltip::after { right: var(--_floating-arrow-size); } + +/* RIGHT */ +.right #tooltip::after { right: calc(100% - 0.5 * var(--_floating-arrow-size)); } +.right.center #tooltip::after { top: calc(50% - 0.5 * var(--_floating-arrow-size)); } +.right.start #tooltip::after { top: var(--_floating-arrow-size); } +.right.end #tooltip::after { bottom: var(--_floating-arrow-size); } + +/* BOTTOM */ +.bottom #tooltip::after { bottom: calc(100% - 0.5 * var(--_floating-arrow-size)); } +.bottom.center #tooltip::after { right: calc(50% - 0.5 * var(--_floating-arrow-size)); } +.bottom.start #tooltip::after { left: var(--_floating-arrow-size); } +.bottom.end #tooltip::after { right: var(--_floating-arrow-size); } + diff --git a/elements/pf-v5-tooltip/pf-v5-tooltip.ts b/elements/pf-v5-tooltip/pf-v5-tooltip.ts new file mode 100644 index 0000000000..12f1682dba --- /dev/null +++ b/elements/pf-v5-tooltip/pf-v5-tooltip.ts @@ -0,0 +1,184 @@ +import type { PropertyValues, TemplateResult } from 'lit'; +import { LitElement, html, isServer } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; +import { styleMap } from 'lit/directives/style-map.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { + FloatingDOMController, + type Placement, +} from '@patternfly/pfe-core/controllers/floating-dom-controller.js'; + +import { bound } from '@patternfly/pfe-core/decorators/bound.js'; + +import { StringListConverter } from '@patternfly/pfe-core'; + +import styles from './pf-v5-tooltip.css'; + +const EnterEvents = ['focusin', 'tap', 'click', 'mouseenter']; +const ExitEvents = ['focusout', 'blur', 'mouseleave']; + +/** + * A **tooltip** is in-app messaging used to identify elements on a page with short, + * clarifying text. + * @summary Toggle the visibility of helpful or contextual information. + * @alias Tooltip + */ +@customElement('pf-v5-tooltip') +export class PfV5Tooltip extends LitElement { + static readonly styles: CSSStyleSheet[] = [styles]; + + /** The position of the tooltip, relative to the invoking content */ + @property() position: Placement = 'top'; + + /** Tooltip content. Overridden by the content slot */ + @property() content?: string; + + /** If false, prevents the tooltip from trying to remain in view by flipping itself when necessary */ + @property({ type: Boolean, attribute: 'no-flip' }) noFlip = false; + + @property() trigger?: string | Element; + + /** + * The flip order when flip is enabled and the initial position is not possible. + * There are 12 options: `top`, `bottom`, `left`, `right`, `top-start`, `top-end`, + * `bottom-start`, `bottom-end`, `left-start`, `left-end`,`right-start`, `right-end`. + * The default is [oppositePlacement], where only the opposite placement is tried. + */ + @property({ + attribute: 'flip-behavior', + converter: StringListConverter, + }) flipBehavior?: Placement[]; + + get #invoker(): HTMLSlotElement | null { + return this.shadowRoot?.querySelector?.('#invoker') ?? null; + } + + get #content(): HTMLElement | null { + return this.shadowRoot?.querySelector?.('#tooltip') ?? null; + } + + #referenceTrigger?: HTMLElement | null; + + #float = new FloatingDOMController(this, { + content: (): HTMLElement | null | undefined => this.#content, + invoker: (): HTMLElement | null | undefined => { + if (this.#referenceTrigger) { + return this.#referenceTrigger; + } else if (this.#invoker instanceof HTMLSlotElement + && this.#invoker.assignedElements().length > 0) { + return this.#invoker.assignedElements().at(0) as HTMLElement; + } else { + return this.#invoker; + } + }, + }); + + override connectedCallback(): void { + super.connectedCallback(); + this.#invokerChanged(); + this.#updateTrigger(); + } + + /** + * Removes event listeners from the old trigger element and attaches + * them to the new trigger element. + * @param changed changed properties + */ + override willUpdate(changed: PropertyValues): void { + if (changed.has('trigger')) { + this.#updateTrigger(); + } + } + + override render(): TemplateResult<1> { + const { alignment, anchor, open, styles } = this.#float; + + const blockInvoker = + this.#invoker?.assignedElements().length === 0 + && this.#invoker?.assignedNodes().length > 0; + const display = blockInvoker ? 'block' : 'contents'; + + return html` +
+
+ + +
+
+ + ${this.content} +
+
+ `; + } + + /** the invoker slot should render at block level if it only has text nodes */ + #invokerChanged() { + this.requestUpdate(); + } + + #getReferenceTrigger() { + return (this.getRootNode() as Document | ShadowRoot) + .getElementById(this.trigger?.normalize() ?? ''); + } + + #updateTrigger() { + if (!isServer) { + const oldReferenceTrigger = this.#referenceTrigger; + this.#referenceTrigger = + this.trigger instanceof HTMLElement ? this.trigger + : typeof this.trigger === 'string' ? this.#getReferenceTrigger() + : null; + for (const evt of EnterEvents) { + if (this.#referenceTrigger) { + this.removeEventListener(evt, this.show); + this.#referenceTrigger.addEventListener(evt, this.show); + } else { + oldReferenceTrigger?.removeEventListener(evt, this.show); + this.addEventListener(evt, this.show); + } + } + for (const evt of ExitEvents) { + if (this.#referenceTrigger) { + this.removeEventListener(evt, this.hide); + this.#referenceTrigger.addEventListener(evt, this.hide); + } else { + oldReferenceTrigger?.removeEventListener(evt, this.hide); + this.addEventListener(evt, this.hide); + } + } + } + } + + @bound async show(): Promise { + await this.updateComplete; + const placement = this.position; + const offset = + !placement?.match(/top|bottom/) ? 15 + : { mainAxis: 15, alignmentAxis: -4 }; + await this.#float.show({ + offset, + placement, + flip: !this.noFlip, + fallbackPlacements: this.flipBehavior, + }); + } + + @bound async hide(): Promise { + await this.#float.hide(); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pf-v5-tooltip': PfV5Tooltip; + } +} diff --git a/elements/pf-tooltip/test/pf-tooltip.e2e.ts b/elements/pf-v5-tooltip/test/pf-tooltip.e2e.ts similarity index 95% rename from elements/pf-tooltip/test/pf-tooltip.e2e.ts rename to elements/pf-v5-tooltip/test/pf-tooltip.e2e.ts index 0c645f9bba..e51a80a7fb 100644 --- a/elements/pf-tooltip/test/pf-tooltip.e2e.ts +++ b/elements/pf-v5-tooltip/test/pf-tooltip.e2e.ts @@ -2,7 +2,7 @@ import { test } from '@playwright/test'; import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js'; import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js'; -const tagName = 'pf-tooltip'; +const tagName = 'pf-v5-tooltip'; test.describe(tagName, () => { test('snapshot', async ({ page }) => { diff --git a/elements/pf-tooltip/test/pf-tooltip.spec.ts b/elements/pf-v5-tooltip/test/pf-tooltip.spec.ts similarity index 76% rename from elements/pf-tooltip/test/pf-tooltip.spec.ts rename to elements/pf-v5-tooltip/test/pf-tooltip.spec.ts index db8ae23c17..06317a94d9 100644 --- a/elements/pf-tooltip/test/pf-tooltip.spec.ts +++ b/elements/pf-v5-tooltip/test/pf-tooltip.spec.ts @@ -1,12 +1,12 @@ import { expect, html, fixture } from '@open-wc/testing'; import type { A11yTreeSnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; -import { PfTooltip } from '../pf-tooltip.js'; +import { PfV5Tooltip } from '../pf-v5-tooltip.js'; import { setViewport, sendMouse } from '@web/test-runner-commands'; import { a11ySnapshot } from '@patternfly/pfe-tools/test/a11y-snapshot.js'; -describe('', function() { - let element: PfTooltip; +describe('', function() { + let element: PfV5Tooltip; let snapshot: A11yTreeSnapshot; beforeEach(async function() { @@ -14,22 +14,22 @@ describe('', function() { }); it('imperatively instantiates', function() { - expect(document.createElement('pf-tooltip')).to.be.an.instanceof(PfTooltip); + expect(document.createElement('pf-v5-tooltip')).to.be.an.instanceof(PfV5Tooltip); }); it('should upgrade', async function() { - element = await fixture(html``); - const klass = customElements.get('pf-tooltip'); + element = await fixture(html``); + const klass = customElements.get('pf-v5-tooltip'); expect(element) .to.be.an.instanceOf(klass) .and - .to.be.an.instanceOf(PfTooltip); + .to.be.an.instanceOf(PfV5Tooltip); }); describe('typical usage', function() { beforeEach(async function() { - element = await fixture(html` - Tooltip + element = await fixture(html` + Tooltip `); snapshot = await a11ySnapshot(); }); diff --git a/eleventy.config.cjs b/eleventy.config.cjs index 2edd455ccd..93e8299221 100644 --- a/eleventy.config.cjs +++ b/eleventy.config.cjs @@ -19,6 +19,8 @@ const markdownItAnchor = require('markdown-it-anchor'); const { $ } = require('execa'); const path = require('node:path'); +const _slugify = require('@sindresorhus/slugify'); +const slugify = _slugify.default ?? _slugify; /** @param {import('@11ty/eleventy/src/UserConfig')} eleventyConfig */ module.exports = function(eleventyConfig) { @@ -34,7 +36,7 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPlugin(TocPlugin, { tags: ['h2', 'h3', 'h4'] }); /** Copy and manage site assets from the monorepo */ - eleventyConfig.addPlugin(PfeAssetsPlugin); + eleventyConfig.addPlugin(PfeAssetsPlugin, { prefix: 'pf-v5' }); /** Generate and consume custom elements manifests */ eleventyConfig.addPlugin(CustomElementsManifestPlugin); @@ -91,12 +93,11 @@ module.exports = function(eleventyConfig) { if ( !existingids.includes($.attr('id')) && $.attr('slot') - && $.closest('pf-card') + && $.closest('pf-v5-card') ) { return null; } else { - return eleventyConfig.javascript.functions - .slug($.text()) + return slugify($.text(), { decamelize: false }) .replace(/[&,+()$~%.'":*?!<>{}]/g, ''); } }, diff --git a/eslint.config.js b/eslint.config.js index 6fbc4dc5e4..9cac24974b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -13,7 +13,7 @@ export default tseslint.config( { name: 'local/ignores', ignores: [ - 'elements/pf-icon/demo/icons/**/*.js', + 'elements/pf-v5-icon/demo/icons/**/*.js', 'tools/create-element/templates/**/*', 'tools/create-element/**/*.js', 'tools/create-element/templates/**/*', diff --git a/package-lock.json b/package-lock.json index f822d73f21..75fd510ee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,11 +17,11 @@ "@actions/glob": "^0.5.0", "@commitlint/cli": "^19.4.0", "@commitlint/config-conventional": "^19.2.2", - "@jspm/generator": "~2.7.6", "@lit/react": "^1.0.8", "@octokit/core": "^6.1.2", "@patternfly/patternfly": "^4.224.5", "@pwrs/cem": "^0.7.5", + "@pwrs/mappa": "^0.0.4", "@rhds/elements": "^4.0.1", "@types/koa__router": "^12.0.5", "@types/node": "^22.19.0", @@ -59,7 +59,7 @@ }, "core/pfe-core": { "name": "@patternfly/pfe-core", - "version": "5.0.5", + "version": "5.0.8", "license": "MIT", "dependencies": { "@lit/context": "^1.1.6", @@ -68,12 +68,12 @@ }, "elements": { "name": "@patternfly/elements", - "version": "4.3.0", + "version": "4.4.0", "license": "MIT", "dependencies": { "@lit/context": "^1.1.6", "@patternfly/icons": "^1.0.3", - "@patternfly/pfe-core": "^5.0.5", + "@patternfly/pfe-core": "^5.0.8", "lit": "^3.3.2", "tslib": "^2.8.1" } @@ -412,249 +412,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", @@ -664,142 +421,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", - "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -810,51 +431,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@changesets/apply-release-plan": { "version": "7.0.14", "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.14.tgz", @@ -2186,31 +1762,12 @@ "node": ">=12" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.0.0" } @@ -2219,57 +1776,20 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jspm/generator": { - "version": "2.7.6", - "resolved": "https://registry.npmjs.org/@jspm/generator/-/generator-2.7.6.tgz", - "integrity": "sha512-rAUFW/+7kORaUngC+jevgK00mzD30wC4tErjneVZ4LDHuUYa44OQ/o78Hd3pYlf+tHocMyW7rQD52pt8jhW6lA==", - "license": "Apache-2.0", - "dependencies": { - "@babel/core": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/preset-typescript": "^7.24.7", - "@jspm/import-map": "^1.2.2", - "es-module-lexer": "^1.5.4", - "minimatch": "^10.0.1", - "node-fetch-cache": "^5.0.2", - "pako": "^2.1.0", - "sver": "^1.8.4", - "tar-stream": "^3.1.6" - } - }, - "node_modules/@jspm/generator/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jspm/import-map": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jspm/import-map/-/import-map-1.2.2.tgz", - "integrity": "sha512-QrM7+lkgVE8t9NZSm9fDSzheguEOVCOxueKxSwgoOt7yUWesWKDFmmL9bQFURl4IqMcpPz7VV2Tvw9s9emaJzQ==", - "license": "MIT" - }, "node_modules/@koa/router": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/@koa/router/-/router-15.1.1.tgz", @@ -2575,18 +2095,6 @@ "node": ">= 8" } }, - "node_modules/@npmcli/fs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", - "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@octokit/auth-token": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", @@ -2981,49 +2489,148 @@ "node": ">=22.0.0" } }, - "node_modules/@pwrs/cem-win32-arm64": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@pwrs/cem-win32-arm64/-/cem-win32-arm64-0.7.5.tgz", - "integrity": "sha512-9qA6D5EdP4wry43jIMGIdw32EfCkfMW3yP+502i4L32z1z98giIkQLcDkC7gcjGLb4jL3bAZORWILDFuPyw12w==", + "node_modules/@pwrs/cem-win32-arm64": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@pwrs/cem-win32-arm64/-/cem-win32-arm64-0.7.5.tgz", + "integrity": "sha512-9qA6D5EdP4wry43jIMGIdw32EfCkfMW3yP+502i4L32z1z98giIkQLcDkC7gcjGLb4jL3bAZORWILDFuPyw12w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@pwrs/cem-win32-x64": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@pwrs/cem-win32-x64/-/cem-win32-x64-0.7.5.tgz", + "integrity": "sha512-CLftjDlJO2TeCPLWGfXoJrXs05iGbEuxj8oN06RMml1c/hoKgNmUWxnqooTdWioTkfkOHr2QiyKu7i60LekikQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@pwrs/lit-css": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@pwrs/lit-css/-/lit-css-4.0.0.tgz", + "integrity": "sha512-yz8dGXCZJJBvg8/riSlf8XGrNbktLd639XlCtKyVsc8xFdq9Sa+ZFJi66GEk5D3YrYSj3JmFLFh1fETR77CSmw==", + "license": "ISC", + "peer": true, + "dependencies": { + "cssnano": "^7.0.7" + } + }, + "node_modules/@pwrs/mappa": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@pwrs/mappa/-/mappa-0.0.4.tgz", + "integrity": "sha512-Bz+ZoDm2XzaqsL1hvRAaiauE1UDSlHAn/iODHy7Brs7h5J8VAszlCAlb4BYxvqnN5GFFG9in0NTpSSQajV5HvA==", + "hasInstallScript": true, + "license": "GPL-3.0-or-later", + "bin": { + "mappa": "dist/cli.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "@pwrs/mappa-darwin-arm64": "0.0.4", + "@pwrs/mappa-darwin-x64": "0.0.4", + "@pwrs/mappa-linux-arm64": "0.0.4", + "@pwrs/mappa-linux-x64": "0.0.4", + "@pwrs/mappa-win32-arm64": "0.0.4", + "@pwrs/mappa-win32-x64": "0.0.4" + } + }, + "node_modules/@pwrs/mappa-darwin-arm64": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@pwrs/mappa-darwin-arm64/-/mappa-darwin-arm64-0.0.4.tgz", + "integrity": "sha512-3lobYl5Uws8gj0LIuOVhmRQbcbTJw6XhNhSZpU+jhSq0VdXrZrrMVP8/tcONqFteu6HJxtsrU45GATUjYF9r5A==", + "cpu": [ + "arm64" + ], + "license": "GPL-3.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pwrs/mappa-darwin-x64": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@pwrs/mappa-darwin-x64/-/mappa-darwin-x64-0.0.4.tgz", + "integrity": "sha512-NHd4Zddn7vzQz9Eu5OytbbwUj0YA+SSOa7m1FLQ+EU95gARefLm4FDz3XHncHdAsh85xWhn13JcLyabeOuKg/w==", + "cpu": [ + "x64" + ], + "license": "GPL-3.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pwrs/mappa-linux-arm64": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@pwrs/mappa-linux-arm64/-/mappa-linux-arm64-0.0.4.tgz", + "integrity": "sha512-LdL884a9nFYEkPvazUTSYeprlxe2C9p0euR7gh3sGfnooFKZ0ULqD0Zr112DJmNhjSHlkxTgo4Dx8nTi6VKXUw==", + "cpu": [ + "arm64" + ], + "license": "GPL-3.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pwrs/mappa-linux-x64": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@pwrs/mappa-linux-x64/-/mappa-linux-x64-0.0.4.tgz", + "integrity": "sha512-mgr1pWjLouD+uMAZZ973gbb3sOJJhX0KO4mf5iSVlwG4NUMvo3kQIcHX9bryINzVZ9w64KZbt0Pi1UWm8oNJpQ==", + "cpu": [ + "x64" + ], + "license": "GPL-3.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pwrs/mappa-win32-arm64": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@pwrs/mappa-win32-arm64/-/mappa-win32-arm64-0.0.4.tgz", + "integrity": "sha512-v6i1LgLr2DQD2NTdziimWbKAi/pzvU/mYV9fgpzzsH3Jj65MzfOosyxbeHOOe3RfidRcsvNkJd3WB1tQCUZH7Q==", "cpu": [ "arm64" ], - "dev": true, - "license": "MIT", + "license": "GPL-3.0", "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=22.0.0" - } + ] }, - "node_modules/@pwrs/cem-win32-x64": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@pwrs/cem-win32-x64/-/cem-win32-x64-0.7.5.tgz", - "integrity": "sha512-CLftjDlJO2TeCPLWGfXoJrXs05iGbEuxj8oN06RMml1c/hoKgNmUWxnqooTdWioTkfkOHr2QiyKu7i60LekikQ==", + "node_modules/@pwrs/mappa-win32-x64": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@pwrs/mappa-win32-x64/-/mappa-win32-x64-0.0.4.tgz", + "integrity": "sha512-en5JXuv0VHtw9PgWDFoIvLRm8Wzb6gUYPE38Usz+jeJRI2k7d0t5wuQ0d0VzoFQQSwfjXxj7Cbka4nb9tdvmjQ==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", + "license": "GPL-3.0", "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@pwrs/lit-css": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@pwrs/lit-css/-/lit-css-4.0.0.tgz", - "integrity": "sha512-yz8dGXCZJJBvg8/riSlf8XGrNbktLd639XlCtKyVsc8xFdq9Sa+ZFJi66GEk5D3YrYSj3JmFLFh1fETR77CSmw==", - "license": "ISC", - "peer": true, - "dependencies": { - "cssnano": "^7.0.7" - } + ] }, "node_modules/@rhds/elements": { "version": "4.0.1", @@ -5844,6 +5451,7 @@ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "license": "Apache-2.0", + "peer": true, "peerDependencies": { "react-native-b4a": "*" }, @@ -5864,6 +5472,7 @@ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "license": "Apache-2.0", + "peer": true, "peerDependencies": { "bare-abort-controller": "*" }, @@ -5960,6 +5569,7 @@ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "license": "Apache-2.0", + "peer": true, "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -6095,6 +5705,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6142,85 +5753,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", - "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^5.0.0", - "fs-minipass": "^3.0.0", - "glob": "^13.0.0", - "lru-cache": "^11.1.0", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^13.0.0", - "unique-filename": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", - "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/cache-content-type": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", @@ -6349,7 +5881,8 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0" + "license": "CC-BY-4.0", + "peer": true }, "node_modules/case": { "version": "1.6.3", @@ -7000,7 +6533,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cookies": { "version": "0.9.1", @@ -7306,6 +6840,7 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "license": "MIT", + "peer": true, "engines": { "node": ">= 12" } @@ -7789,7 +7324,8 @@ "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/element-internals-polyfill": { "version": "3.0.2", @@ -7995,7 +7531,8 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/es-module-shims": { "version": "2.7.0", @@ -8993,6 +8530,7 @@ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "bare-events": "^2.7.0" } @@ -9106,7 +8644,8 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-glob": { "version": "3.3.3", @@ -9198,6 +8737,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -9367,20 +8907,12 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/formdata-node": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", - "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", + "peer": true, "dependencies": { "fetch-blob": "^3.1.2" }, @@ -9413,18 +8945,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -9469,15 +8989,6 @@ "node": ">= 0.4" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -10738,6 +10249,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", + "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -10814,18 +10326,6 @@ "node": ">= 0.6.0" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonc-eslint-parser": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", @@ -11499,12 +10999,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/locko": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/locko/-/locko-1.1.0.tgz", - "integrity": "sha512-pYB2dzRY93fJkg2RIl41AMNgTQftEjyTK9vlPrGOJvuGQsOjb267VJBw15BjiN3RBd1oBoKkOu9E2dRdFKIfAA==", - "license": "MIT" - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -11701,15 +11195,6 @@ "license": "MIT", "peer": true }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/luxon": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", @@ -12030,78 +11515,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", @@ -12243,6 +11656,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=10.5.0" } @@ -12252,6 +11666,7 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", + "peer": true, "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -12265,26 +11680,12 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-fetch-cache": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-fetch-cache/-/node-fetch-cache-5.1.0.tgz", - "integrity": "sha512-4j3rRHNGIKGX7VzXSrBT0bh7+wFuyJv1DxCfCLDHsnDahJWoD9lXe3BzL3BJg/GEIJiM7KIvqVs3byW1GFtRsQ==", - "license": "MIT", - "dependencies": { - "cacache": "^20.0.1", - "formdata-node": "^6.0.3", - "locko": "^1.1.0", - "node-fetch": "3.3.2" - }, - "engines": { - "node": ">=18.19.0" - } - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/node-retrieve-globals": { "version": "6.0.1", @@ -12699,18 +12100,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", @@ -12784,12 +12173,6 @@ "quansync": "^0.2.7" } }, - "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, "node_modules/param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -15160,18 +14543,6 @@ "license": "BSD-3-Clause", "peer": true }, - "node_modules/ssri": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", - "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -15187,6 +14558,7 @@ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "license": "MIT", + "peer": true, "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", @@ -15398,25 +14770,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sver": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", - "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", - "license": "MIT", - "optionalDependencies": { - "semver": "^6.3.0" - } - }, - "node_modules/sver/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/svgo": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", @@ -15528,6 +14881,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "license": "MIT", + "peer": true, "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -15552,6 +14906,7 @@ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "b4a": "^1.6.4" } @@ -16043,30 +15398,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unique-filename": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", - "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", - "license": "ISC", - "dependencies": { - "unique-slug": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/unique-slug": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", - "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -16112,6 +15443,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -16300,6 +15632,7 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } @@ -16585,12 +15918,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, "node_modules/yaml": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", @@ -16769,7 +16096,7 @@ }, "tools/create-element": { "name": "@patternfly/create-element", - "version": "1.0.5", + "version": "1.0.6", "license": "MIT", "dependencies": { "case": "^1.6.3", @@ -16905,7 +16232,7 @@ }, "tools/pfe-tools": { "name": "@patternfly/pfe-tools", - "version": "5.0.5", + "version": "6.0.1", "license": "MIT", "devDependencies": { "@types/dedent": "^0.7.2", @@ -16927,11 +16254,11 @@ "@11ty/eleventy-plugin-directory-output": "^1.0.2", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2", "@changesets/cli": "^2.29.8", - "@jspm/generator": "~2.7.6", "@koa/router": "^15.1.1", "@lit-labs/ssr": "^4.0.0", "@open-wc/testing": "^4.0.0", "@playwright/test": "~1.48.0", + "@pwrs/mappa": "^0.0.4", "@rollup/plugin-replace": "^6.0.3", "@web/dev-server": "^0.4.6", "@web/dev-server-esbuild": "^1.0.4", diff --git a/package.json b/package.json index cea4bdb900..e67eb08243 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "build:lightdom", "build:elements", "build:create", - "build:bundle", "build:react" ] }, @@ -137,21 +136,6 @@ "core/**/*.tsbuildinfo" ] }, - "build:bundle": { - "command": "node scripts/bundle.js", - "dependencies": [ - "build:core" - ], - "files": [ - "elements/**/*.ts", - "elements/**/*.css", - "core/**/*.ts", - "scripts/bundle.js" - ], - "output": [ - "elements/pfe.min.js" - ] - }, "build:react": { "command": "npx tsx scripts/generate-react-wrappers.ts", "dependencies": [ @@ -184,6 +168,7 @@ ], "dependencies": [ "build:core", + "build:elements", "analyze" ] }, @@ -207,7 +192,8 @@ "service": true, "command": "eleventy --serve", "dependencies": [ - "build:core" + "build:core", + "build:elements" ] }, "start": { @@ -285,11 +271,11 @@ "@actions/glob": "^0.5.0", "@commitlint/cli": "^19.4.0", "@commitlint/config-conventional": "^19.2.2", - "@jspm/generator": "~2.7.6", "@lit/react": "^1.0.8", "@octokit/core": "^6.1.2", "@patternfly/patternfly": "^4.224.5", "@pwrs/cem": "^0.7.5", + "@pwrs/mappa": "^0.0.4", "@rhds/elements": "^4.0.1", "@types/koa__router": "^12.0.5", "@types/node": "^22.19.0", diff --git a/patches/@jspm+generator+2.7.6.patch b/patches/@jspm+generator+2.7.6.patch deleted file mode 100644 index 60e65f7da8..0000000000 --- a/patches/@jspm+generator+2.7.6.patch +++ /dev/null @@ -1,41 +0,0 @@ -diff --git a/node_modules/@jspm/generator/dist/generator-790b7aa7.js b/node_modules/@jspm/generator/dist/generator-790b7aa7.js -index 269fffc..dc7527c 100644 ---- a/node_modules/@jspm/generator/dist/generator-790b7aa7.js -+++ b/node_modules/@jspm/generator/dist/generator-790b7aa7.js -@@ -3314,6 +3314,9 @@ async function extractLockConstraintsAndMap(log, map, preloadUrls, mapUrl, rootU - } - if (installSubpath !== false) { - setResolution(locks, parsedKey.pkgName, pkgUrl, null, installSubpath); -+ if (key.endsWith('/')) { -+ maps.imports[key] = map.imports[key] -+ } - return; - } - } -@@ -3825,7 +3828,7 @@ function _define_property$2(obj, key, value) { - return obj; - } - function combineSubpaths(installSubpath, traceSubpath) { -- if (traceSubpath.endsWith('/')) throw new Error('Trailing slash subpaths unsupported'); -+ if (traceSubpath.endsWith('/')) return traceSubpath; - return installSubpath === null || installSubpath === '.' || traceSubpath === '.' ? installSubpath || traceSubpath : `${installSubpath}${traceSubpath.slice(1)}`; - } - class TraceMap { -@@ -3869,7 +3872,7 @@ class TraceMap { - // very likely that the user has some CommonJS dependencies, but this is - // something that the user has to explicitly enable: - if (isBuiltinScheme(resolved)) return null; -- if (resolved.endsWith('/')) throw new JspmError(`Trailing "/" installs not supported installing ${resolved} for ${parentUrl}`); -+ if (resolved.endsWith('/')) return; - try { - var entry = await this.resolver.analyze(resolved); - } catch (e) { -@@ -5112,7 +5115,7 @@ function createFetchOptions(cache = true, fetchOptions = {}) { - replacer.remove(existingComment.start, existingComment.end, true); - } - } -- replacer.replace(analysis.map.start, analysis.map.end, (comment ? '' + newlineTab : '') + esms + '' + preloads + (analysis.map.newScript ? newlineTab : '')); -+ replacer.replace(analysis.map.start, analysis.map.end, (comment ? '' + newlineTab : '') + esms + '' + preloads + (analysis.map.newScript ? newlineTab : '')); - return replacer.source; - } - async install(install, mode) { diff --git a/scripts/bundle.js b/scripts/bundle.js deleted file mode 100755 index 9253115d2c..0000000000 --- a/scripts/bundle.js +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node -/* globals process */ -import { build } from 'esbuild'; -import { join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { litCssPlugin } from 'esbuild-plugin-lit-css'; -import { glob } from 'glob'; - -import postcss from 'postcss'; -import postcssNesting from 'postcss-nesting'; - -const resolveDir = join(fileURLToPath(import.meta.url), '../../elements'); - -const entryPoints = - (await glob('./pf-*/pf-*.ts', { cwd: resolveDir })) - .map(x => x.replace('.ts', '.js')); - -const contents = entryPoints.map(x => `export * from './${x.replaceAll('\\', '/')}';`).join('\n'); - -/** - * @param {object} options - * @param {object} options.outfile file path to bundle to - */ -export async function bundle({ outfile = 'elements/pfe.min.js' } = {}) { - await build({ - stdin: { - contents, - loader: 'ts', - resolveDir, - }, - format: 'esm', - outfile, - allowOverwrite: true, - treeShaking: true, - legalComments: 'linked', - logLevel: 'info', - sourcemap: true, - bundle: true, - minify: true, - minifyWhitespace: true, - - external: [ - 'lit', - 'tslib', - ], - - plugins: [ - litCssPlugin({ - cssnano: false, - filter: /\.css$/, - transform: (str, { filePath }) => - postcss(postcssNesting()) - .process(str, { from: filePath }) - .css, - }), - ], - }); -} - -if (process.argv.at(1) === import.meta.url) { - try { - await bundle(); - } catch { - process.exit(1); - } -} diff --git a/tools/pfe-tools/11ty/DocsPage.ts b/tools/pfe-tools/11ty/DocsPage.ts index 8fc1fba249..108687687b 100644 --- a/tools/pfe-tools/11ty/DocsPage.ts +++ b/tools/pfe-tools/11ty/DocsPage.ts @@ -1,4 +1,5 @@ import type { PfeConfig } from '../config.js'; +import { getPfeConfig } from '../config.js'; import { Manifest } from '../custom-elements-manifest/lib/Manifest.js'; import slugify from 'slugify'; @@ -20,12 +21,14 @@ export class DocsPage { summary?: string | null; docsTemplatePath?: string; constructor(public manifest: Manifest, options?: DocsPageOptions) { + const config = getPfeConfig(options?.rootDir); this.tagName = options?.tagName ?? ''; - this.title = options?.title ?? Manifest.prettyTag(this.tagName); this.docsTemplatePath = options?.docsTemplatePath; this.summary = this.manifest.getSummary(this.tagName); this.description = this.manifest.getDescription(this.tagName); - const aliased = options?.aliases?.[this.tagName] ?? this.tagName.replace(/^\w+-/, ''); + const prefix = `${config.tagPrefix.replace(/-$/, '')}-`; + const aliased = config.aliases[this.tagName] ?? this.tagName.replace(prefix, ''); + this.title = options?.title ?? Manifest.prettyTag(this.tagName, config.aliases, prefix); this.slug = slugify(aliased, { strict: true, lower: true }); } } diff --git a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts index d70a27f199..266e9ae229 100644 --- a/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts +++ b/tools/pfe-tools/custom-elements-manifest/lib/Manifest.ts @@ -175,11 +175,12 @@ export class Manifest { public static prettyTag = ( tagName: string, aliases?: Record, - ): string => aliases?.[tagName] ?? tagName - .replace(/^\w+-/, '') - .toLowerCase() - .replace(/(?:^|[-/\s])\w/g, x => x.toUpperCase()) - .replace(/-/g, ' '); + prefix?: string, + ): string => aliases?.[tagName] + ?? (prefix ? tagName.replace(prefix, '') : tagName.replace(/^\w+-/, '')) + .toLowerCase() + .replace(/(?:^|[-/\s])\w/g, x => x.toUpperCase()) + .replace(/-/g, ' '); declarations: Map = new Map(); @@ -323,10 +324,12 @@ export class Manifest { const [last = ''] = filePath.split(path.sep).reverse(); const filename = last.replace('.html', ''); const isMainElementDemo = filename === 'index'; - const title = isMainElementDemo ? options.aliases[tagName] ?? prettyTag(tagName) : last - .replace(/(?:^|[-/\s])\w/g, x => x.toUpperCase()) - .replace(/-/g, ' ') - .replace('.html', ''); + const prefix = `${options.tagPrefix.replace(/-$/, '')}-`; + const title = isMainElementDemo ? prettyTag(tagName, options.aliases, prefix) + : last + .replace(/(?:^|[-/\s])\w/g, x => x.toUpperCase()) + .replace(/-/g, ' ') + .replace('.html', ''); return { tagName, primaryElementName, diff --git a/tools/pfe-tools/dev-server/bundle.ts b/tools/pfe-tools/dev-server/bundle.ts index ec0d93708f..e36ecb1bc3 100644 --- a/tools/pfe-tools/dev-server/bundle.ts +++ b/tools/pfe-tools/dev-server/bundle.ts @@ -1,3 +1,3 @@ -export * from '@patternfly/elements/pf-icon/pf-icon.js'; +export * from '@patternfly/elements/pf-v5-icon/pf-v5-icon.js'; import './components.js'; diff --git a/tools/pfe-tools/dev-server/config.ts b/tools/pfe-tools/dev-server/config.ts index 6685fb72a5..806e9a182f 100644 --- a/tools/pfe-tools/dev-server/config.ts +++ b/tools/pfe-tools/dev-server/config.ts @@ -52,7 +52,6 @@ function normalizeOptions(options?: PfeDevServerConfigOptions) { .replace(/\/node_modules$/, '/') .replace('//', '/'); config.importMapOptions ??= {} as PfeDevServerConfigOptions['importMapOptions']; - config.importMapOptions!.providers ??= {}; config.site = { ...config.site, ...options?.site ?? {} }; config.loadDemo ??= true; config.watchFiles ??= '{elements,core}/**/*.{css,html}'; @@ -147,26 +146,7 @@ export function pfeDevServerConfig(options?: PfeDevServerConfigOptions): DevServ // load .css files as lit CSSResult modules litCss(config.litcssOptions), - importMapGeneratorPlugin({ - ...config.importMapOptions, - providers: { - 'construct-style-sheets-polyfill': 'nodemodules', - 'element-internals-polyfill': 'nodemodules', - 'lit-html': 'nodemodules', - 'lit': 'nodemodules', - '@lit/reactive-element': 'nodemodules', - '@lit/context': 'nodemodules', - ...config.importMapOptions?.providers, - }, - resolveHtmlUrl(fileUrl, rootUrl) { - const override = config.importMapOptions.resolveHtmlUrl?.(fileUrl, rootUrl); - if (override) { - return override; - } else { - return fileUrl.replace('/components/', '/elements/pf-'); - } - }, - }), + importMapGeneratorPlugin(config.importMapOptions), ...config?.plugins ?? [], diff --git a/tools/pfe-tools/dev-server/plugins/dev-server-router.ts b/tools/pfe-tools/dev-server/plugins/dev-server-router.ts index 8e25bb6e55..3f467f190b 100644 --- a/tools/pfe-tools/dev-server/plugins/dev-server-router.ts +++ b/tools/pfe-tools/dev-server/plugins/dev-server-router.ts @@ -1,13 +1,13 @@ import type { PfeDevServerInternalConfig } from './pfe-dev-server.js'; -import Router, { type RouterMiddleware } from '@koa/router'; +import Router from '@koa/router'; import type { DefaultState, DefaultContext, Middleware } from 'koa'; import { makeDemoEnv } from '../../environment.js'; import { deslugify } from '../../config.js'; type PfeMiddleware = - (config: PfeDevServerInternalConfig) => RouterMiddleware; + (config: PfeDevServerInternalConfig) => Middleware; /** * The environment file contains information from the serverside diff --git a/tools/pfe-tools/dev-server/plugins/import-map-generator.ts b/tools/pfe-tools/dev-server/plugins/import-map-generator.ts index e6a56acbb6..9c0aa6c494 100644 --- a/tools/pfe-tools/dev-server/plugins/import-map-generator.ts +++ b/tools/pfe-tools/dev-server/plugins/import-map-generator.ts @@ -1,129 +1,41 @@ import type { Middleware } from 'koa'; import type { Plugin } from '@web/dev-server-core'; -import type { GeneratorOptions, Provider } from '@jspm/generator'; -import { readFile, stat } from 'node:fs/promises'; +import { resolve } from '@pwrs/mappa'; -import { join } from 'node:path'; - -import { Generator } from '@jspm/generator'; - -import { glob } from 'node:fs/promises'; - -export interface Options extends GeneratorOptions { +export interface Options { resolveHtmlUrl?: (fileUrl: string, rootUrl: string) => string; + providers?: Record; + inputMap?: { imports?: Record; scopes?: Record> }; + resolutions?: Record; + ignore?: string[]; + typeScript?: boolean; } -const exists = async (path: string) => { - try { - await stat(path); return true; - } catch { - return false; - } -}; - -async function getPotentialPackageDirs(cwd: string, workspaces: string[]) { - const potentialPackageDirs: string[] = []; - await Promise.all( - (workspaces ?? []).map(async (pattern: string) => { - for await (const dir of glob(pattern, { cwd })) { - potentialPackageDirs.push(dir); - } - }) - ); - return potentialPackageDirs; -} - -async function resolveMonorepoPackages() { - const cwd = process.cwd(); - - const { workspaces } = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf-8')); - - const potentialPackageDirs = await getPotentialPackageDirs(cwd, workspaces); - const packages = new Map(); - - for (const dir of ['.', ...potentialPackageDirs]) { - const pkgDir = join(cwd, dir); - const pkgJsonPath = join(pkgDir, 'package.json'); - if (await exists(pkgJsonPath)) { - const { name } = JSON.parse(await readFile(pkgJsonPath, 'utf-8')); - packages.set(name, pkgDir); - } - } - - return packages; -} - -function getProvider(packages: Map): Provider { - return { - async pkgToUrl({ name }) { - const pkgPath = packages.get(name); - if (!pkgPath) { - throw new Error(`could not resolve ${name}`); - } - return `file://${pkgPath}/`; - }, - // @ts-expect-error: types vs docs - parseUrlPkg(url: string) { - for (const [name, dir] of packages) { - if (url.startsWith(dir)) { - return { - name, - registry: 'monorepotypescript', - version: '*', - }; - } - } - }, - async resolveLatestTarget(target) { - return { ...target, version: '*' }; - }, - }; +interface ImportMap { + imports?: Record; + scopes?: Record>; } -function generatorMiddleware(generator: Generator, options?: Partial): Middleware { +function importMapMiddleware(importMap: ImportMap): Middleware { return async function injectMiddleware(ctx, next) { - if (ctx.path.endsWith('.html') || ctx.path.endsWith('/')) { - if (ctx.body?.length) { - const rootUrl = `file://${process.cwd()}/`; - const fileUrl = `${rootUrl.replace(/\/$/, '')}${ctx.url}`; - const htmlUrl = options?.resolveHtmlUrl?.(fileUrl, rootUrl) ?? fileUrl; - try { - ctx.body = await generator.htmlInject(ctx.body, { - htmlUrl, - rootUrl, - trace: true, - whitespace: true, - esModuleShims: true, - }); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - } - } + await next(); + if ((ctx.path.endsWith('.html') || ctx.path.endsWith('/')) && ctx.body?.length) { + const script = ``; + ctx.body = (ctx.body as string).replace('', `${script}\n`); } - return next(); }; } -/** - * @param options plugin options - */ export function importMapGeneratorPlugin(options?: Partial): Plugin { return { name: 'import-map-inject', async serverStart(args) { - const monorepotypescript = getProvider(await resolveMonorepoPackages()); - args.app.use(generatorMiddleware(new Generator({ - defaultProvider: 'nodemodules', - env: ['development', 'production', 'browser', 'module'], - providers: options?.providers, + const importMap = await resolve('.', { + conditions: ['development', 'production', 'browser', 'import', 'default'], inputMap: options?.inputMap, - customProviders: { monorepotypescript }, - resolutions: options?.resolutions, - ignore: options?.ignore, - typeScript: options?.typeScript, - }), options)); + }); + args.app.use(importMapMiddleware(importMap)); }, }; } diff --git a/tools/pfe-tools/dev-server/plugins/templates/index.html b/tools/pfe-tools/dev-server/plugins/templates/index.html index 18b76dc009..a765c86f5a 100644 --- a/tools/pfe-tools/dev-server/plugins/templates/index.html +++ b/tools/pfe-tools/dev-server/plugins/templates/index.html @@ -36,8 +36,8 @@ @@ -45,12 +45,12 @@
- - - + + {{ title }} @@ -123,7 +123,7 @@
{% for primary, group in groupeddemos %} {% set first = group['0'] %} - +

{{ first.title }}

{{ primary }} @@ -133,7 +133,7 @@

{{ first.title }}

{{ d.title }} {% endif %}{% endfor %} -
{% endfor %} + {% endfor %}
{% endif %} diff --git a/tools/pfe-tools/package.json b/tools/pfe-tools/package.json index ea6c753dc4..a48a09aba4 100644 --- a/tools/pfe-tools/package.json +++ b/tools/pfe-tools/package.json @@ -67,11 +67,11 @@ "@11ty/eleventy-plugin-directory-output": "^1.0.2", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2", "@changesets/cli": "^2.29.8", - "@jspm/generator": "~2.7.6", "@koa/router": "^15.1.1", "@lit-labs/ssr": "^4.0.0", "@open-wc/testing": "^4.0.0", "@playwright/test": "~1.48.0", + "@pwrs/mappa": "^0.0.4", "@rollup/plugin-replace": "^6.0.3", "@web/dev-server": "^0.4.6", "@web/dev-server-esbuild": "^1.0.4", diff --git a/tools/pfe-tools/test/create-fixture.ts b/tools/pfe-tools/test/create-fixture.ts index 1a8ec544ba..31f586eaa2 100644 --- a/tools/pfe-tools/test/create-fixture.ts +++ b/tools/pfe-tools/test/create-fixture.ts @@ -1,6 +1,6 @@ import type { TemplateResult } from 'lit'; import { chai, fixtureCleanup, fixture } from '@open-wc/testing'; -// @ts-expect-error: colorjs.io types not resolved with Node moduleResolution +// @ts-ignore: colorjs.io types not resolved with Node moduleResolution on Windows CI import Color from 'colorjs.io'; /** diff --git a/tsconfig.settings.json b/tsconfig.settings.json index ef69951558..7d56602e06 100644 --- a/tsconfig.settings.json +++ b/tsconfig.settings.json @@ -74,6 +74,9 @@ ], "@patternfly/pfe-tools/*": [ "./tools/pfe-tools/*" + ], + "@pwrs/mappa": [ + "./node_modules/@pwrs/mappa/dist/mappa.d.ts" ] } }