diff --git a/.github/workflows/client_tests.yml b/.github/workflows/client_tests.yml index 7d44e099d..998eb7d52 100644 --- a/.github/workflows/client_tests.yml +++ b/.github/workflows/client_tests.yml @@ -4,6 +4,7 @@ name: Client Tests on: + workflow_dispatch: push: branches: [next] pull_request: diff --git a/.prettierignore b/.prettierignore index 52ba57889..3da34c16a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,18 +1,23 @@ +# Files are relative to .prettierignore at the root of this monorepo. +# + *.hbs *.md -!docs/docs/patterns/**/*.md +!packages/webawesome/docs/docs/patterns/**/*.md docs/docs/patterns/blog-news/post-list.md -.cache +**/*/.cache .github cspell.json -dist -docs/search.json -src/components/icon/icons -src/react/index.ts +packages/**/*/dist +packages/**/*/dist-cdn +packages/**/*/docs/search.json +packages/**/*/src/components/icon/icons +packages/**/*/src/react/index.ts +**/*/package.json +**/*/package-lock.json +**/*/tsconfig.json +**/*/tsconfig.prod.json node_modules -package.json -package-lock.json -tsconfig.json -cdn -_site -docs/assets/scripts/prism-downloaded.js + +packages/**/*/_site +packages/webawesome/docs/assets/scripts/prism-downloaded.js diff --git a/cspell.json b/cspell.json index 29b93af05..f6f397bb8 100644 --- a/cspell.json +++ b/cspell.json @@ -125,6 +125,7 @@ "noreferrer", "novalidate", "Numberish", + "nums", "oklab", "oklch", "onscrollend", @@ -139,6 +140,7 @@ "progressbar", "radiogroup", "Railsbyte", + "referrerpolicy", "remixicon", "reregister", "resizer", @@ -165,6 +167,7 @@ "slotchange", "smartquotes", "spacebar", + "srcdoc", "stylesheet", "svgs", "Tabbable", diff --git a/package-lock.json b/package-lock.json index 187730bfd..9498eef31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2488,6 +2488,10 @@ "resolved": "packages/webawesome", "link": true }, + "node_modules/@shoelace-style/webawesome-pro": { + "resolved": "packages/webawesome-pro", + "link": true + }, "node_modules/@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -13987,6 +13991,25 @@ "engines": { "node": ">=14.17.0" } + }, + "packages/webawesome-pro": { + "name": "@shoelace-style/webawesome-pro", + "version": "3.0.0-alpha.13", + "license": "TODO", + "dependencies": { + "@ctrl/tinycolor": "^4.1.0", + "@floating-ui/dom": "^1.6.13", + "@shoelace-style/animations": "^1.2.0", + "@shoelace-style/localize": "^3.2.1", + "composed-offset-position": "^0.0.6", + "lit": "^3.2.1", + "qr-creator": "^1.0.0", + "style-observer": "^0.0.7" + }, + "devDependencies": {}, + "engines": { + "node": ">=14.17.0" + } } } } diff --git a/packages/webawesome/.gitignore b/packages/webawesome/.gitignore new file mode 100644 index 000000000..489dbe635 --- /dev/null +++ b/packages/webawesome/.gitignore @@ -0,0 +1,4 @@ +_site +dist +dist-cdn +src/react diff --git a/packages/webawesome/custom-elements-manifest.js b/packages/webawesome/custom-elements-manifest.js index 51f3d0b2a..5597c0c2b 100644 --- a/packages/webawesome/custom-elements-manifest.js +++ b/packages/webawesome/custom-elements-manifest.js @@ -3,9 +3,9 @@ import { customElementVsCodePlugin } from 'custom-element-vs-code-integration'; // import { customElementVuejsPlugin } from 'custom-element-vuejs-integration'; import { parse } from 'comment-parser'; import fs from 'fs'; +import * as path from 'node:path'; import { pascalCase } from 'pascal-case'; import * as url from 'url'; -import * as path from "node:path" const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); const packageData = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8')); @@ -186,4 +186,3 @@ export default { // }) ], }; - diff --git a/packages/webawesome/docs/.eleventy.js b/packages/webawesome/docs/.eleventy.js index 5bdfc377a..48b2361c1 100644 --- a/packages/webawesome/docs/.eleventy.js +++ b/packages/webawesome/docs/.eleventy.js @@ -1,5 +1,5 @@ +import * as fs from 'node:fs'; import * as path from 'node:path'; -import * as fs from "node:fs" import { anchorHeadingsPlugin } from './_utils/anchor-headings.js'; import { codeExamplesPlugin } from './_utils/code-examples.js'; import { copyCodePlugin } from './_utils/copy-code.js'; @@ -41,7 +41,7 @@ export default async function (eleventyConfig) { */ const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4']; - const docsDir = path.join(process.env.BASE_DIR || ".", 'docs'); + const docsDir = path.join(process.env.BASE_DIR || '.', 'docs'); const passThrough = [...passThroughExtensions.map(ext => path.join(docsDir, '**/*.' + ext))]; /** @@ -176,9 +176,8 @@ export default async function (eleventyConfig) { // eleventyConfig.addPlugin(formatCodePlugin()); // } - - let assetsDir = path.join(process.env.BASE_DIR || "docs", "assets") - fs.cpSync(assetsDir, path.join(eleventyConfig.directories.output, "assets"), { recursive: true }) + let assetsDir = path.join(process.env.BASE_DIR || 'docs', 'assets'); + fs.cpSync(assetsDir, path.join(eleventyConfig.directories.output, 'assets'), { recursive: true }); for (let glob of passThrough) { eleventyConfig.addPassthroughCopy(glob); @@ -210,7 +209,6 @@ export default async function (eleventyConfig) { // } } - export const config = { markdownTemplateEngine: 'njk', dir: { @@ -219,5 +217,4 @@ export const config = { layouts: '_layouts', }, templateFormats: ['njk', 'md'], -} - +}; diff --git a/packages/webawesome/docs/_data/componentList.js b/packages/webawesome/docs/_data/componentList.js index 9bc6eabc2..b212e5738 100644 --- a/packages/webawesome/docs/_data/componentList.js +++ b/packages/webawesome/docs/_data/componentList.js @@ -2,13 +2,13 @@ * @module components Fetches components from custom-elements.json and exposes them in a saner format. */ import { readFileSync } from 'fs'; -import { join, dirname, resolve } from 'path'; +import { dirname, join, resolve } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const customElementsJSON = process.env.DIST_DIR - ? join(process.env.DIST_DIR, "custom-elements.json") - : resolve(__dirname, '../../dist/custom-elements.json') + ? join(process.env.DIST_DIR, 'custom-elements.json') + : resolve(__dirname, '../../dist/custom-elements.json'); const manifest = JSON.parse(readFileSync(customElementsJSON), 'utf-8'); @@ -76,4 +76,3 @@ components.sort((a, b) => { }); export default components; - diff --git a/packages/webawesome/docs/_data/componentsBy.js b/packages/webawesome/docs/_data/componentsBy.js deleted file mode 100644 index a40340286..000000000 --- a/packages/webawesome/docs/_data/componentsBy.js +++ /dev/null @@ -1,43 +0,0 @@ -import components from './components.js'; - -const by = { - attribute: {}, - slot: {}, - event: {}, - method: {}, - cssPart: {}, - cssProperty: {}, -}; - -function getAll(component, type) { - let prop = type + 's'; - if (type === 'cssProperty') { - prop = 'cssProperties'; - } - - return component[prop] ?? []; -} - -for (const componentName in components) { - const component = components[componentName]; - - for (const type of ['attribute', 'slot', 'event', 'method', 'cssPart', 'cssProperty']) { - for (const item of getAll(component, type)) { - by[type][item.name] ??= []; - by[type][item.name].push(component); - } - } -} - -// Sort by descending number of components -const sortByLengthDesc = (a, b) => b[1].length - a[1].length; - -for (const key in by) { - by[key] = sortObject(by[key], sortByLengthDesc); -} - -export default by; - -function sortObject(obj, sorter) { - return Object.fromEntries(Object.entries(obj).sort(sorter)); -} diff --git a/packages/webawesome/docs/_includes/page-demo.njk b/packages/webawesome/docs/_includes/page-demo.njk index 031145a15..fc22ab978 100644 --- a/packages/webawesome/docs/_includes/page-demo.njk +++ b/packages/webawesome/docs/_includes/page-demo.njk @@ -15,9 +15,8 @@ {% endfor %} - - - + + - - - ${preview} - - - `); - const codeExample = parse(` -
+
- - ${isViewportDemo ? ` ` : preview} - + ${preview} diff --git a/packages/webawesome/docs/_utils/copy-code.js b/packages/webawesome/docs/_utils/copy-code.js index 3b56ae933..93f8ed526 100644 --- a/packages/webawesome/docs/_utils/copy-code.js +++ b/packages/webawesome/docs/_utils/copy-code.js @@ -32,7 +32,7 @@ export function copyCodePlugin(eleventyConfig, options = {}) { } // Add a copy button - pre.innerHTML += ` + pre.innerHTML += ` `; }); diff --git a/packages/webawesome/docs/assets/examples/page/demo-1.html b/packages/webawesome/docs/assets/examples/page/demo-1.html index 603043cd7..7a4688b34 100644 --- a/packages/webawesome/docs/assets/examples/page/demo-1.html +++ b/packages/webawesome/docs/assets/examples/page/demo-1.html @@ -32,7 +32,9 @@
@@ -167,7 +184,9 @@ - + + +
  • @@ -177,7 +196,9 @@
  • @@ -187,7 +208,9 @@
  • @@ -197,7 +220,9 @@
  • @@ -207,7 +232,9 @@
  • @@ -217,7 +244,9 @@
  • @@ -227,7 +256,9 @@
  • @@ -237,7 +268,9 @@
  • @@ -247,7 +280,9 @@
  • @@ -260,7 +295,9 @@
  • @@ -273,7 +310,9 @@
  • @@ -286,7 +325,9 @@
  • @@ -380,8 +421,8 @@ aspect-ratio: 1; color: var(--wa-color-brand-fill-loud); display: flex; - height: var(--flank-size); justify-content: center; + padding-block: 0.5em; } #recent wa-icon { border-radius: var(--wa-border-radius-s); @@ -420,16 +461,14 @@ [slot='main-header'] { background-color: var(--wa-color-surface-raised); } - #play-controls wa-icon-button::part(base) { + #play-controls wa-button::part(base) { border: var(--wa-border-width-l) var(--wa-border-style) currentColor; border-radius: var(--wa-border-radius-circle); font-size: 1.5rem; } - #play-controls wa-icon-button[name='play']::part(base) { - background-color: var(--wa-color-brand-fill-loud); + #play-controls wa-button:has(wa-icon[name='play'])::part(base) { border: none; - color: var(--wa-color-brand-on-loud); - font-size: 3rem; + font-size: 2.5rem; padding: 0.5em 0.45em 0.5em 0.55em; } [slot='main-footer'].wa-grid > * { diff --git a/packages/webawesome/docs/assets/scripts/preset-theme-picker.js b/packages/webawesome/docs/assets/scripts/preset-theme-picker.js index 9965d8c37..90953cd2e 100644 --- a/packages/webawesome/docs/assets/scripts/preset-theme-picker.js +++ b/packages/webawesome/docs/assets/scripts/preset-theme-picker.js @@ -1,4 +1,4 @@ -import { domChange, nextFrame, ThemeAspect } from './theme-picker.js'; +import { domChange, ThemeAspect } from './theme-picker.js'; const presetTheme = new ThemeAspect({ defaultValue: 'default', @@ -33,7 +33,7 @@ const presetTheme = new ThemeAspect({ if (instant) { // If no VT, delay by 1 frame to make it smoother - await nextFrame(); + await new Promise(requestAnimationFrame); } oldStylesheet.remove(); diff --git a/packages/webawesome/docs/assets/scripts/prism-downloaded.js b/packages/webawesome/docs/assets/scripts/prism-downloaded.js index be9653dd6..1fea307c3 100644 --- a/packages/webawesome/docs/assets/scripts/prism-downloaded.js +++ b/packages/webawesome/docs/assets/scripts/prism-downloaded.js @@ -6,3 +6,4 @@ Prism.languages.markup={comment:{pattern://,greedy:!0 Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript; !function(){if("undefined"!=typeof Prism){var n,s,a="";Prism.plugins.customClass={add:function(s){n=s},map:function(n){s="function"==typeof n?n:function(s){return n[s]||s}},prefix:function(n){a=n||""},apply:t},Prism.hooks.add("wrap",(function(e){if(n){var u=n({content:e.content,type:e.type,language:e.language});Array.isArray(u)?e.classes.push.apply(e.classes,u):u&&e.classes.push(u)}(s||a)&&(e.classes=e.classes.map((function(n){return t(n,e.language)})))}))}function t(n,t){return a+(s?s(n,t):n)}}(); + diff --git a/packages/webawesome/docs/assets/scripts/scroll.js b/packages/webawesome/docs/assets/scripts/scroll.js index 8a2bbe113..609c29d5e 100644 --- a/packages/webawesome/docs/assets/scripts/scroll.js +++ b/packages/webawesome/docs/assets/scripts/scroll.js @@ -1,3 +1,19 @@ +import { allDefined } from '/dist/webawesome.js'; + +/** + * Determines how the page was loaded. Possible return values include "reload", "navigate", "back_forward", "prerender", + * and "unknown". + */ +function getNavigationType() { + if (performance.getEntriesByType) { + const navEntries = performance.getEntriesByType('navigation'); + if (navEntries.length > 0) { + return navEntries[0].type; + } + } + return 'unknown'; +} + // Smooth links document.addEventListener('click', event => { const link = event.target.closest('a'); @@ -31,3 +47,26 @@ function updateScrollClass() { window.addEventListener('scroll', updateScrollClass); window.addEventListener('turbo:render', updateScrollClass); updateScrollClass(); + +// Restore scroll position after components are defined +allDefined().then(() => { + const navigationType = getNavigationType(); + const key = `wa-scroll-y-[${location.pathname}]`; + const scrollY = sessionStorage.getItem(key); + + // Only restore when reloading, otherwise clear it + if (navigationType === 'reload' && scrollY) { + window.scrollTo(0, scrollY); + } else { + sessionStorage.removeItem(key); + } + + // After restoring, keep tabs on the page's scroll position for next reload + window.addEventListener( + 'scroll', + () => { + sessionStorage.setItem(key, window.scrollY); + }, + { passive: true }, + ); +}); diff --git a/packages/webawesome/docs/assets/scripts/sidebar-tweaks.js b/packages/webawesome/docs/assets/scripts/sidebar-tweaks.js index 168004c53..8d4a1bb24 100644 --- a/packages/webawesome/docs/assets/scripts/sidebar-tweaks.js +++ b/packages/webawesome/docs/assets/scripts/sidebar-tweaks.js @@ -79,10 +79,12 @@ const sidebar = { let append = [...badges]; if (entity.delete) { - let deleteButton = Object.assign(document.createElement('wa-icon-button'), { - name: 'trash', - label: 'Delete', + let deleteButton = Object.assign(document.createElement('wa-button'), { + appearance: 'plain', + variant: 'danger', + size: 'small', className: 'delete', + innerHTML: '', }); deleteButton.addEventListener('click', () => entity.delete()); append.push(deleteButton); diff --git a/packages/webawesome/docs/assets/scripts/theme-picker.js b/packages/webawesome/docs/assets/scripts/theme-picker.js index 1fb301e1a..1a39ee50a 100644 --- a/packages/webawesome/docs/assets/scripts/theme-picker.js +++ b/packages/webawesome/docs/assets/scripts/theme-picker.js @@ -1,14 +1,11 @@ import { domChange } from './util/dom-change.js'; export { domChange }; -export function nextFrame() { - return new Promise(resolve => requestAnimationFrame(resolve)); -} - export class ThemeAspect { constructor(options) { Object.assign(this, options); this.set(); + this.syncIframes(); // Update when local storage changes. // That way changes in one window will propagate to others (including iframes). @@ -67,6 +64,30 @@ export class ThemeAspect { this.syncUI(); } + async syncIframes() { + await customElements.whenDefined('wa-zoomable-frame'); + await new Promise(requestAnimationFrame); + + // Sync to wa-zoomable-frame iframes + let dark = this.computedValue === 'dark'; + for (let zoomableEl of document.querySelectorAll('wa-zoomable-frame')) { + const iframe = zoomableEl.iframe; + + const applyToIframe = () => { + try { + iframe.contentDocument.documentElement.classList.toggle('wa-dark', dark); + } catch (e) { + // Silently handle access issues + } + }; + + // Try immediately + applyToIframe(); + // Also listen for load in case it wasn't ready + iframe.addEventListener('load', applyToIframe, { once: true }); + } + } + syncUI(container = document) { for (let picker of container.querySelectorAll(this.picker)) { picker.setAttribute('value', this.value); @@ -87,27 +108,22 @@ const colorScheme = new ThemeAspect({ }, applyChange() { - // Toggle the dark mode class - domChange(() => { + // Toggle the dark mode class with view transition + const updateTheme = () => { let dark = this.computedValue === 'dark'; document.documentElement.classList.toggle(`wa-dark`, dark); document.documentElement.dispatchEvent(new CustomEvent('wa-color-scheme-change', { detail: { dark } })); - syncViewportDemoColorSchemes(); - }); + this.syncIframes(); + }; + + if (document.startViewTransition) { + document.startViewTransition(() => domChange(updateTheme)); + } else { + domChange(updateTheme); + } }, }); -function syncViewportDemoColorSchemes() { - const isDark = document.documentElement.classList.contains('wa-dark'); - - // Update viewport demo color schemes in code examples - document.querySelectorAll('.code-example.is-viewport-demo wa-viewport-demo').forEach(demo => { - demo.querySelectorAll('iframe').forEach(iframe => { - iframe.contentWindow.document.documentElement?.classList?.toggle('wa-dark', isDark); - }); - }); -} - // Update the color scheme when the preference changes window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => colorScheme.set()); @@ -121,12 +137,3 @@ document.addEventListener('keydown', event => { colorScheme.set(colorScheme.get() === 'dark' ? 'light' : 'dark'); } }); - -// When rendering a code example with a viewport demo, set the theme to match initially -document.querySelectorAll('.code-example.is-viewport-demo wa-viewport-demo iframe').forEach(iframe => { - const isDark = document.documentElement.classList.contains('wa-dark'); - - iframe.addEventListener('load', () => { - iframe.contentWindow.document.documentElement?.classList?.toggle('wa-dark', isDark); - }); -}); diff --git a/packages/webawesome/docs/assets/styles/code-examples.css b/packages/webawesome/docs/assets/styles/code-examples.css index c945ae45c..5fd30af62 100644 --- a/packages/webawesome/docs/assets/styles/code-examples.css +++ b/packages/webawesome/docs/assets/styles/code-examples.css @@ -116,10 +116,12 @@ padding: 0.5rem; cursor: pointer; - &:hover { - border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet) !important; /* TODO - remove after native styles refactor */ - background: var(--wa-color-surface-default) !important; /* TODO - remove after native styles refactor */ - color: var(--wa-color-text-quiet) !important; /* TODO - remove after native styles refactor */ + @media (hover: hover) { + &:hover { + border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet) !important; /* TODO - remove after native styles refactor */ + background: var(--wa-color-surface-default) !important; /* TODO - remove after native styles refactor */ + color: var(--wa-color-text-quiet) !important; /* TODO - remove after native styles refactor */ + } } &:first-of-type { diff --git a/packages/webawesome/docs/assets/styles/copy-code.css b/packages/webawesome/docs/assets/styles/copy-code.css index f65887b64..a79379669 100644 --- a/packages/webawesome/docs/assets/styles/copy-code.css +++ b/packages/webawesome/docs/assets/styles/copy-code.css @@ -9,8 +9,10 @@ wa-copy-button.copy-button { border-radius: var(--wa-border-radius-m); padding: 0.25rem; - &:hover { - color: white; + @media (hover: hover) { + &:hover { + color: white; + } } &:focus-visible { diff --git a/packages/webawesome/docs/assets/styles/docs.css b/packages/webawesome/docs/assets/styles/docs.css index 7b7f81577..126163490 100644 --- a/packages/webawesome/docs/assets/styles/docs.css +++ b/packages/webawesome/docs/assets/styles/docs.css @@ -193,10 +193,13 @@ wa-badge.pro { } } - wa-icon-button.delete { - vertical-align: -0.2em; + wa-button.delete { margin-inline-start: var(--wa-space-xs); + &:hover wa-icon { + color: var(--wa-color-danger-on-quiet); + } + &:not(li:hover > *, :focus) { opacity: 0; } @@ -208,12 +211,9 @@ wa-badge.pro { } } -wa-icon-button.delete { - &:hover { - color: var(--wa-color-danger-on-quiet); - } - +wa-button.delete { &::part(base):hover { + color: var(--wa-color-danger-on-quiet); background: var(--wa-color-danger-fill-quiet); } @@ -495,8 +495,10 @@ table.colors { tbody { tr { border: none; - &:hover { - background: transparent; + @media (hover: hover) { + &:hover { + background: transparent; + } } } @@ -545,27 +547,6 @@ table.colors { --icon-color: var(--wa-color-success-fill-quiet); } -.icon-modifier { - position: relative; - display: inline-flex; - - .modifier { - position: absolute; - bottom: -0.1em; - right: -0.3em; - font-size: 60%; - - &::part(svg) { - stroke: var(--background-color, var(--wa-color-surface-default)); - stroke-width: 100px; - paint-order: stroke; - overflow: visible; - stroke-linecap: round; - stroke-linejoin: round; - } - } -} - /* Layout Examples */ .layout-example-boundary { border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal); diff --git a/packages/webawesome/docs/assets/styles/theme-headers.css b/packages/webawesome/docs/assets/styles/theme-headers.css index 02192e96c..168e0811a 100644 --- a/packages/webawesome/docs/assets/styles/theme-headers.css +++ b/packages/webawesome/docs/assets/styles/theme-headers.css @@ -124,7 +124,7 @@ html.wa-theme-tailspin .preview-container { &::part(footer) { border: none; } - & wa-icon-button { + & wa-button { color: var(--wa-color-base-50); } } @@ -226,11 +226,11 @@ html.wa-theme-brutalist .preview-container { --wa-color-neutral-border-quiet: color-mix(in oklab, var(--wa-color-gray-30), white 40%); } - .message-composer [slot='header'] wa-icon-button::part(base) { + .message-composer [slot='header'] wa-button::part(base) { color: var(--wa-color-neutral-on-loud); } - .message-composer .grouped-buttons wa-icon-button::part(base):hover { + .message-composer .grouped-buttons wa-button::part(base):hover { background-color: var(--wa-color-neutral-fill-normal); color: var(--wa-color-text-normal); } @@ -421,7 +421,7 @@ html.wa-theme-playful .preview-container { --wa-color-neutral-fill-quiet: var(--wa-color-gray-90); } - .message-composer wa-icon-button { + .message-composer wa-button { color: var(--wa-text-color-normal); font-size: var(--wa-font-size-l); } @@ -662,12 +662,12 @@ html.wa-theme-premium .preview-container { --padding: var(--wa-space-s) var(--wa-space-xl); } - .message-composer .grouped-buttons wa-icon-button::part(base) { + .message-composer .grouped-buttons wa-button::part(base) { block-size: var(--wa-form-control-height-s); inline-size: var(--wa-form-control-height-s); justify-content: center; } - .message-composer .grouped-buttons wa-icon-button::part(base):hover { + .message-composer .grouped-buttons wa-button::part(base):hover { background-color: var(--wa-color-neutral-fill-normal); color: var(--wa-color-text-normal); } diff --git a/packages/webawesome/docs/assets/styles/ui.css b/packages/webawesome/docs/assets/styles/ui.css index f87f65367..3b341ba29 100644 --- a/packages/webawesome/docs/assets/styles/ui.css +++ b/packages/webawesome/docs/assets/styles/ui.css @@ -5,10 +5,11 @@ } .title { - wa-icon-button { - font-size: var(--wa-font-size-l); - color: var(--wa-color-text-quiet); + display: flex; + align-items: center; + gap: var(--wa-space-xs); + wa-button:has(wa-icon) { &:not(:hover, :focus) { opacity: 0.5; } @@ -127,11 +128,11 @@ > input { font: inherit; - margin-block: calc(-1 * var(--wa-space-smaller)); + margin-block: 0.75em; field-sizing: content; } - wa-icon-button { + wa-button { font-size: 90%; } } diff --git a/packages/webawesome/docs/assets/vue/components/editable-text.js b/packages/webawesome/docs/assets/vue/components/editable-text.js index fffe00a82..f2e7b51a0 100644 --- a/packages/webawesome/docs/assets/vue/components/editable-text.js +++ b/packages/webawesome/docs/assets/vue/components/editable-text.js @@ -4,11 +4,15 @@ const template = ` `; diff --git a/packages/webawesome/docs/assets/vue/components/ui-slider.js b/packages/webawesome/docs/assets/vue/components/ui-slider.js index 8b56f6bf0..5d7370028 100644 --- a/packages/webawesome/docs/assets/vue/components/ui-slider.js +++ b/packages/webawesome/docs/assets/vue/components/ui-slider.js @@ -8,7 +8,9 @@ const template = `
    - + + +
    diff --git a/packages/webawesome/docs/docs/components/button.md b/packages/webawesome/docs/docs/components/button.md index 2d1d98206..5cabbf00a 100644 --- a/packages/webawesome/docs/docs/components/button.md +++ b/packages/webawesome/docs/docs/components/button.md @@ -103,6 +103,19 @@ It's often helpful to have a button that works like a link. This is possible by Download ``` +### Icon Buttons + +When only an [icon](/docs/components/icon) is slotted into the `label` slot, the button becomes an icon button. In this case, it's important to give the icon a label for users with assistive devices. Icon buttons can use any appearance or variant. + +```html {.example} + +``` + ### Setting a Custom Width As expected, buttons can be given a custom width by setting the `width` CSS property. This is useful for making buttons span the full width of their container on smaller screens. diff --git a/packages/webawesome/docs/docs/components/card.md b/packages/webawesome/docs/docs/components/card.md index 1795dd39e..1100f7b3e 100644 --- a/packages/webawesome/docs/docs/components/card.md +++ b/packages/webawesome/docs/docs/components/card.md @@ -57,7 +57,9 @@ If using SSR, you need to also use the `with-header` attribute to add a header t This card has a header. You can put all sorts of things in it! diff --git a/packages/webawesome/docs/docs/components/cheatsheet/cheatsheet.js b/packages/webawesome/docs/docs/components/cheatsheet/cheatsheet.js deleted file mode 100644 index a52adb275..000000000 --- a/packages/webawesome/docs/docs/components/cheatsheet/cheatsheet.js +++ /dev/null @@ -1,76 +0,0 @@ -let url = new URL(location); -const pushedURL = false; - -const matchers = { - default(textContent, query) { - return textContent.includes(query); - }, - - i(textContent, query) { - return textContent.toLowerCase().includes(query.toLowerCase()); - }, - - regexp(textContent, query) { - query.lastIndex = 0; - return query.test(textContent); - }, -}; - -matchers.iregexp = matchers.regexp; // i is baked into the query - -function filterByName(value) { - const previousFilter = url.searchParams.get('name') || ''; - url = new URL(location); - - if (value) { - const isRegexp = name_search_regexp.checked; - const i = !name_search_i.checked; - const query = isRegexp ? new RegExp(value, 'gmsv' + (i ? 'i' : '')) : value; - const matcherId = (i ? 'i' : '') + (isRegexp ? 'regexp' : ''); - const matcher = matchers[matcherId] ?? matchers.default; - - for (const th of document.querySelectorAll('table tbody th:first-child')) { - const tr = th.parentNode; - const matches = matcher(th.textContent, query); - tr.toggleAttribute('hidden', !matches); - } - url.searchParams.set('name', value); - - if (matcherId) { - url.searchParams.set('match', matcherId); - } else { - url.searchParams.delete('match'); - } - } else { - for (const tr of document.querySelectorAll('table tbody tr[hidden]')) { - tr.removeAttribute('hidden'); - } - url.searchParams.delete('name'); - url.searchParams.delete('match'); - } - - if (value !== previousFilter) { - history[pushedURL ? 'replaceState' : 'pushState'](null, '', url); - } - - // Update heading counts - for (const h2 of document.querySelectorAll('h2:has(+ table)')) { - const count = h2.querySelector('.count'); - if (!count) continue; - const table = h2.nextElementSibling; - const visibleRows = table.querySelectorAll('tbody tr:not([hidden])').length; - count.textContent = visibleRows; - const outlineLink = document.querySelector(`#outline-standard a[href="#${h2.id}"]`); - if (outlineLink) { - // Why not just = h2.textContent? To skip the "Jump to heading" link - outlineLink.textContent = ''; - outlineLink.append(...[...h2.childNodes].slice(0, 3).map(n => n.cloneNode(true))); - } - } -} - -if (name_search.value) { - filterByName(name_search.value); -} - -name_search_group.addEventListener('input', e => filterByName(name_search.value)); diff --git a/packages/webawesome/docs/docs/components/cheatsheet/index.njk b/packages/webawesome/docs/docs/components/cheatsheet/index.njk deleted file mode 100644 index e7dde94c1..000000000 --- a/packages/webawesome/docs/docs/components/cheatsheet/index.njk +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: Component Cheatsheet -layout: docs -unlisted: true ---- - - - -

    - This page lists every bit of syntax used by every Web Awesome component and which components share it. - For these times when your memory is failing, or to simply explore the possibilities! -

    - -
    - Filter by name - - Case sensitive - Regular expression -
    - - - - -{% for type, all in componentsBy -%} -{% set typeTitle = "CSS custom properties" if type == "cssProperty" else ("CSS parts" if type == "cssPart" else (type | title) + "s") %} -

    - All {{ (all | keys).length }} - {{ typeTitle }} -

    - - - - - - - - - {% for name, thingComponents in all -%} - - - - - {%- endfor %} -
    NameComponents
    {{ name }}{{ "()" if type == "method" }} - {% set componentLinks = [] %} - {% for component in thingComponents %} - {%- set link -%} - <{{ component.tagName }}> - {%- endset -%} - {# https://giuliachiola.dev/posts/add-items-to-an-array-in-nunjucks/ #} - {% set componentLinks = (componentLinks.push(link), componentLinks) %} - {%- endfor -%} - {% if thingComponents.length > 1 %} -
    - {{ thingComponents.length }} components - {{ componentLinks | safe }} -
    - {% else %} - {{ componentLinks | safe }} - {% endif %} -
    - -{%- endfor %} diff --git a/packages/webawesome/docs/docs/components/details.md b/packages/webawesome/docs/docs/components/details.md index 9f6d94107..0678f6c4a 100644 --- a/packages/webawesome/docs/docs/components/details.md +++ b/packages/webawesome/docs/docs/components/details.md @@ -49,7 +49,7 @@ Use the `expand-icon` and `collapse-icon` slots to change the expand and collaps ``` -### HTML in summary +### HTML in Summary To use HTML in the summary, use the `summary` slot. Links and other interactive elements will still retain their behavior: @@ -67,7 +67,7 @@ Links and other interactive elements will still retain their behavior: ``` -### Right-to-Left languages +### Right-to-Left Languages The details component automatically adapts to right-to-left languages: @@ -104,40 +104,23 @@ Use the `appearance` attribute to change the element’s visual appearance. ### Grouping Details -Details are designed to function independently, but you can simulate a group or "accordion" where only one is shown at a time by listening for the `wa-show` event. +Use the `name` attribute to create accordion-like behavior where only one details element with the same name can be open at a time. This matches the behavior of native `
    ` elements. ```html {.example} -
    - - Lorem ipsum dolor sit amet, consectetur adipiscing 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. + - - - - -``` +``` \ No newline at end of file diff --git a/packages/webawesome/docs/docs/components/dialog.md b/packages/webawesome/docs/docs/components/dialog.md index b1f6ef8e5..9670315f2 100644 --- a/packages/webawesome/docs/docs/components/dialog.md +++ b/packages/webawesome/docs/docs/components/dialog.md @@ -67,24 +67,28 @@ Footers can be used to display titles and more. Use the `footer` slot to add a f ``` -### Dismissing Dialogs +### Opening and Closing Dialogs Declaratively -You can add the special `data-dialog="close"` attribute to a button inside the dialog to tell it to close without additional JavaScript. Alternatively, you can set the `open` property to `false` to close the dialog programmatically. +You can open and close dialogs with JavaScript by toggling the `open` attribute, but you can also do it declaratively. Add the `data-dialog="open id"` to any button on the page, where `id` is the ID of the dialog you want to open. ```html {.example} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close -Open Dialog +Open Dialog +``` - +```html {.example} + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Close + + +Open Dialog ``` ### Custom Width @@ -131,11 +135,13 @@ By design, a dialog's height will never exceed that of the viewport. As such, di ### Header Actions -The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/docs/components/icon-button) if needed. +The header shows a functional close button by default. You can use the `header-actions` slot to add additional [buttons](/docs/components/button) if needed. ```html {.example} - + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close diff --git a/packages/webawesome/docs/docs/components/drawer.md b/packages/webawesome/docs/docs/components/drawer.md index 80a499fad..5001b2cef 100644 --- a/packages/webawesome/docs/docs/components/drawer.md +++ b/packages/webawesome/docs/docs/components/drawer.md @@ -65,24 +65,28 @@ Footers can be used to display titles and more. Use the `footer` slot to add a f ``` -### Dismissing Drawers +### Opening and Closing Drawers Declaratively -You can add the special `data-drawer="close"` attribute to a button inside the drawer to tell it to close without additional JavaScript. Alternatively, you can set the `open` property to `false` to close the drawer programmatically. +You can open and close drawers with JavaScript by toggling the `open` attribute, but you can also do it declaratively. Add the `data-drawer="open id"` to any button on the page, where `id` is the ID of the drawer you want to open. ```html {.example} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close -Open Drawer +Open Drawer +``` - +```html {.example} + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Close + + +Open Drawer ``` ### Slide in From Start @@ -189,11 +193,13 @@ By design, a drawer's height will never exceed 100% of its container. As such, d ### Header Actions -The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/docs/components/icon-button) if needed. +The header shows a functional close button by default. You can use the `header-actions` slot to add additional [buttons](/docs/components/button) if needed. ```html {.example} - + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close diff --git a/packages/webawesome/docs/docs/components/icon-button.md b/packages/webawesome/docs/docs/components/icon-button.md deleted file mode 100644 index e8490e915..000000000 --- a/packages/webawesome/docs/docs/components/icon-button.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Icon Button -description: Icons buttons are simple, icon-only buttons that can be used for actions and in toolbars. -tags: [actions, apps] -icon: icon-button ---- - -For a full list of icons that come bundled with Web Awesome, refer to the [icon component](/docs/components/icon). - -```html {.example} - -``` - -## Examples - -### Sizes - -Icon buttons inherit their parent element's `font-size`. - -```html {.example} - - - -``` - -### Colors - -Icon buttons are designed to have a uniform appearance, so their color is not inherited. However, you can still customize them by styling the `base` part. - -```html {.example} -
    - - - -
    - - -``` - -### Link Buttons - -Use the `href` attribute to convert the button to a link. - -```html {.example} - -``` - -### Icon Button with Tooltip - -Add a tooltip that references the `id` of the icon button to provide contextual information. - -```html {.example} - -Settings -``` - -### Disabled - -Use the `disabled` attribute to disable the icon button. - -```html {.example} - -``` diff --git a/packages/webawesome/docs/docs/components/popover.md b/packages/webawesome/docs/docs/components/popover.md new file mode 100644 index 000000000..b4edbdaa6 --- /dev/null +++ b/packages/webawesome/docs/docs/components/popover.md @@ -0,0 +1,143 @@ +--- +title: Popover +layout: component +--- + +Popovers display interactive content when their anchor element is clicked. Unlike [tooltips](/docs/components/tooltip), popovers can contain links, buttons, and form controls. They appear without an overlay and will close when you click outside or press [[Escape]]. Only one popover can be open at a time. + +```html {.example} + +
    +

    This popover contains interactive content that users can engage with directly.

    + Take Action +
    +
    + +Show popover +``` + +## Examples + +### Assigning an Anchor + +Use `` or ` + + + I'm anchored to a native button. + +``` + +:::warning +Make sure the anchor element exists in the DOM before the popover connects. If it doesn't exist, the popover won't attach and you'll see a console warning. +::: + +### Opening and Closing + +Popovers show when you click their anchor element. You can also control them programmatically by setting the `open` property to `true` or `false`. + +Use `data-popover="close"` on any button inside a popover to close it automatically. + +```html {.example} + +

    The button below has data-popover="close" so clicking it will close the popover.

    + Dismiss +
    + +Show popover +``` + +### Placement + +Use the `placement` attribute to set where the popover appears relative to its anchor. The popover will automatically reposition if there isn't enough space in the preferred location. The default placement is `top`. + +```html {.example} +
    + Top + I'm on the top + + Bottom + I'm on the bottom + + Left + I'm on the left + + Right + I'm on the right +
    +``` + +### Distance + +Use the `distance` attribute to control how far the popover appears from its anchor. + +```html {.example} +
    + Near + I'm very close + + Far + I'm farther away +
    +``` + +### Arrow Size + +Use the `--arrow-size` custom property to change the size of the popover's arrow. Set it to `0` to remove the arrow entirely. + +```html {.example} +
    + Big arrow + I have a big arrow + + No arrow + I don't have an arrow +
    +``` + +### Setting a Maximum Width + +Use the `--max-width` custom property to control the maximum width of the popover. + +```html {.example} +Toggle me + + Popovers will usually grow to be much wider, but this one has a custom max width that forces text to wrap. + +``` + +### Setting Focus + +Use the [`autofocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) global attribute to move focus to a specific form control when the popover opens. + +```html {.example} + +
    + + + Submit + +
    +
    + + + + Feedback + +``` \ No newline at end of file diff --git a/packages/webawesome/docs/docs/components/progress-bar.md b/packages/webawesome/docs/docs/components/progress-bar.md index 187587d9b..985e38dbb 100644 --- a/packages/webawesome/docs/docs/components/progress-bar.md +++ b/packages/webawesome/docs/docs/components/progress-bar.md @@ -38,15 +38,19 @@ Use the default slot to show a value. 50%
    - - + + + + + +
    + + +
    + + + + + + + +``` + +### Range selection + +Use the `range` attribute to enable dual-thumb selection for choosing a range of values. Set the initial thumb positions with the `min-value` and `max-value` attributes. + +```html {.example} + + $0 + $50 + $100 + + + +``` + +For range sliders, the `minValue` and `maxValue` properties represent the current positions of the thumbs. When the form is submitted, both values will be included as separate entries with the same name. + +```ts +const slider = document.querySelector('wa-slider[range]'); + +// Get the current values +console.log(`Min value: ${slider.minValue}, Max value: ${slider.maxValue}`); + +// Set the values programmatically +slider.minValue = 30; +slider.maxValue = 70; +``` + +### Vertical Sliders + +Set the `orientation` attribute to `vertical` to create a vertical slider. Vertical sliders automatically center themselves and fill the available vertical space. + +```html {.example} +
    + + + + + +
    +``` + +Range sliders can also be vertical. + +```html {.example} +
    + + +
    + + +``` + +### Size + +Control the slider's size using the `size` attribute. Valid options include `small`, `medium`, and `large`. + +```html {.example} +
    +
    + +``` + +### Indicator Offset + +By default, the filled indicator extends from the minimum value to the current position. Use the `indicator-offset` attribute to change the starting point of this visual indicator. + +```html {.example} + + Lazy + Zoomies + ``` ### Disabled @@ -45,74 +273,17 @@ Use the `min` and `max` attributes to set the range's minimum and maximum values Use the `disabled` attribute to disable a slider. ```html {.example} - + ``` -### Tooltip Placement +### Required -By default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it below the slider. +Mark a slider as required using the `required` attribute. Users must interact with required sliders before the form can be submitted. ```html {.example} - -``` - -### Disable the Tooltip - -To disable the tooltip, set `tooltip` to `none`. - -```html {.example} - -``` - -### Custom Track Colors - -You can customize the active and inactive portions of the track using the `--track-color-active` and `--track-color-inactive` custom properties. - -```html {.example} - -``` - -### Custom Track Offset - -You can customize the initial offset of the active track using the `--track-active-offset` custom property. - -```html {.example} - -``` - -### Custom Tooltip Formatter - -You can change the tooltip's content by setting the `tooltipFormatter` property to a function that accepts the range's value as an argument. - -```html {.example} - - - -``` - -### Right-to-Left languages - -The component adapts to right-to-left (RTL) languages as you would expect. - -```html {.example} - -``` +
    + +
    + +
    +``` \ No newline at end of file diff --git a/packages/webawesome/docs/docs/components/tab-group.md b/packages/webawesome/docs/docs/components/tab-group.md index c93b9d2d5..06d1a9a8d 100644 --- a/packages/webawesome/docs/docs/components/tab-group.md +++ b/packages/webawesome/docs/docs/components/tab-group.md @@ -101,7 +101,9 @@ You can make a tab closable by adding a close button next to the tab and inside General Closable - + + + Advanced This is the general tab panel. @@ -114,17 +116,17 @@ You can make a tab closable by adding a close button next to the tab and inside Restore tab