diff --git a/.github/workflows/ssr_tests.js.yml b/.github/workflows/ssr_tests.js.yml index 1b188eaa2..a5944d604 100644 --- a/.github/workflows/ssr_tests.js.yml +++ b/.github/workflows/ssr_tests.js.yml @@ -1,11 +1,12 @@ -# # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions +# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: SSR Tests on: - push: - branches: [next] + # push: + # branches: [next] + workflow_dispatch: jobs: ssr_test: diff --git a/docs/.eleventy.js b/docs/.eleventy.js index c32049193..fb0dbfd58 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -1,3 +1,4 @@ +import * as path from 'node:path'; import { anchorHeadingsPlugin } from './_utils/anchor-headings.js'; import { codeExamplesPlugin } from './_utils/code-examples.js'; import { copyCodePlugin } from './_utils/copy-code.js'; @@ -6,9 +7,10 @@ import { highlightCodePlugin } from './_utils/highlight-code.js'; import { markdown } from './_utils/markdown.js'; import { removeDataAlphaElements } from './_utils/remove-data-alpha-elements.js'; // import { formatCodePlugin } from './_utils/format-code.js'; -import litPlugin from '@lit-labs/eleventy-plugin-lit'; +// import litPlugin from '@lit-labs/eleventy-plugin-lit'; import { readFile } from 'fs/promises'; -import componentList from './_data/componentList.js'; +import nunjucks from 'nunjucks'; +// import componentList from './_data/componentList.js'; import * as filters from './_utils/filters.js'; import { outlinePlugin } from './_utils/outline.js'; import { replaceTextPlugin } from './_utils/replace-text.js'; @@ -16,7 +18,10 @@ import { searchPlugin } from './_utils/search.js'; import process from 'process'; -const packageData = JSON.parse(await readFile('./package.json', 'utf-8')); +import * as url from 'url'; +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); + +const packageData = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8')); const isAlpha = process.argv.includes('--alpha'); const isDev = process.argv.includes('--develop'); @@ -24,12 +29,22 @@ const globalData = { package: packageData, isAlpha, layout: 'page.njk', + server: { + head: '', + loginOrAvatar: '', + flashes: '', + }, }; const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4']; const passThrough = [...passThroughExtensions.map(ext => 'docs/**/*.' + ext)]; export default function (eleventyConfig) { + /** + * This is the guard we use for now to make sure our final built files dont need a 2nd pass by the server. This keeps us able to still deploy the bare HTML files on Vercel until the app is ready. + */ + const serverBuild = process.env.WEBAWESOME_SERVER === 'true'; + // NOTE - alpha setting removes certain pages if (isAlpha) { eleventyConfig.ignores.add('**/experimental/**'); @@ -55,7 +70,38 @@ export default function (eleventyConfig) { // Shortcodes - {% shortCode arg1, arg2 %} eleventyConfig.addShortcode('cdnUrl', location => { - return `https://early.webawesome.com/webawesome@${packageData.version}/dist/` + location.replace(/^\//, ''); + return `https://early.webawesome.com/webawesome@${packageData.version}/dist/` + (location || '').replace(/^\//, ''); + }); + + // Turns `{% server "foo" %} into `{{ server.foo | safe }}` when the WEBAWESOME_SERVER variable is set to "true" + eleventyConfig.addShortcode('server', function (property) { + if (serverBuild) { + return `{{ server.${property} | safe }}`; + } + + return ''; + }); + + eleventyConfig.addTransform('second-nunjucks-transform', function NunjucksTransform(content) { + // For a server build, we expect a server to run the second transform. + if (serverBuild) { + return content; + } + + // Only run the transform on files nunjucks would transform. + if (!this.page.inputPath.match(/.(md|html|njk)$/)) { + return content; + } + + /** This largely mimics what an app would do and just stubs out what we don't care about. */ + return nunjucks.renderString(content, { + // Stub the server EJS shortcodes. + server: { + head: '', + loginOrAvatar: '', + flashes: '', + }, + }); }); // Paired shortcodes - {% shortCode %}content{% endShortCode %} @@ -117,29 +163,6 @@ export default function (eleventyConfig) { ]), ); - // SSR plugin - if (!isDev) { - // - // Problematic components in SSR land: - // - animation (breaks on navigation + ssr with Turbo) - // - mutation-observer (why SSR this?) - // - resize-observer (why SSR this?) - // - tooltip (why SSR this?) - // - const omittedModules = []; - const componentModules = componentList - .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1])) - .map(component => { - const name = component.tagName.split(/wa-/)[1]; - return `./dist/components/${name}/${name}.js`; - }); - - eleventyConfig.addPlugin(litPlugin, { - mode: 'worker', - componentModules, - }); - } - // Build the search index eleventyConfig.addPlugin( searchPlugin({ @@ -166,6 +189,31 @@ export default function (eleventyConfig) { eleventyConfig.addPassthroughCopy(glob); } + // // SSR plugin + // // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with the nunjucks 2nd transform. + // if (!isDev) { + // // + // // Problematic components in SSR land: + // // - animation (breaks on navigation + ssr with Turbo) + // // - mutation-observer (why SSR this?) + // // - resize-observer (why SSR this?) + // // - tooltip (why SSR this?) + // // + // const omittedModules = []; + // const componentModules = componentList + // .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1])) + // .map(component => { + // const name = component.tagName.split(/wa-/)[1]; + // const componentDirectory = process.env.UNBUNDLED_DIST_DIRECTORY || path.join('.', 'dist'); + // return path.join(componentDirectory, 'components', name, `${name}.js`); + // }); + // + // eleventyConfig.addPlugin(litPlugin, { + // mode: 'worker', + // componentModules, + // }); + // } + return { markdownTemplateEngine: 'njk', dir: { diff --git a/docs/_includes/base.njk b/docs/_includes/base.njk index 0a01532bf..21bfd33a6 100644 --- a/docs/_includes/base.njk +++ b/docs/_includes/base.njk @@ -1,5 +1,5 @@ - + {% include 'head.njk' %} @@ -50,6 +50,9 @@ Search / + + {# Login #} + {% server "loginOrAvatar" %} @@ -76,14 +79,19 @@ {% endif %} + {# Main #}
{# Expandable outline #} + {% if hasOutline %} + {% endif %} + +
{% server "flashes" %}
{% block header %} {% include 'breadcrumbs.njk' %} diff --git a/docs/_includes/breadcrumbs.njk b/docs/_includes/breadcrumbs.njk index 5f9fa145b..96d5563e8 100644 --- a/docs/_includes/breadcrumbs.njk +++ b/docs/_includes/breadcrumbs.njk @@ -1,8 +1,11 @@ -{% set breadcrumbs = page.url | breadcrumbs %} -{% if breadcrumbs.length > 0 %} +{% set ancestors = page.url | ancestors %} + +{% if ancestors.length > 0 %} - {% for crumb in breadcrumbs %} - {{ crumb.title }} + {% for ancestor in ancestors %} + {% if ancestor.page.url != "/" %} + {{ ancestor.data.title }} + {% endif %} {% endfor %} {# Current page #} diff --git a/docs/_includes/grouped-pages.njk b/docs/_includes/grouped-pages.njk index 84cf846f5..2273f4c98 100644 --- a/docs/_includes/grouped-pages.njk +++ b/docs/_includes/grouped-pages.njk @@ -1,12 +1,18 @@ {# Cards for pages listed by category #}
-{% for category, pages in allPages | groupByTags(categories) -%} -

{{ category | getCategoryTitle(categories) }}

- {%- for page in pages -%} - {%- if not page.data.parent or listChildren -%} - {% include "page-card.njk" %} - {%- endif -%} - {%- endfor -%} +{% set groupedPages = allPages | groupPages(categories, page) %} +{% for category, pages in groupedPages -%} + {% if groupedPages.meta.groupCount > 1 and pages.length > 0 %} +

+ {% if pages.meta.url %}{{ pages.meta.title }} + {% else %} + {{ pages.meta.title }} + {% endif %} +

+ {% endif %} + {%- for page in pages -%} + {% include "page-card.njk" %} + {%- endfor -%} {%- endfor -%}
diff --git a/docs/_includes/head.njk b/docs/_includes/head.njk index 695aa836b..5a9808f7b 100644 --- a/docs/_includes/head.njk +++ b/docs/_includes/head.njk @@ -23,10 +23,9 @@ - {# Web Awesome #} - + {# Preset Theme #} @@ -47,3 +46,7 @@ + + +{# Used by Web Awesome App to inject other assets into the head. #} +{% server "head" %} diff --git a/docs/_includes/page-card.njk b/docs/_includes/page-card.njk index f63d813ed..b684a3487 100644 --- a/docs/_includes/page-card.njk +++ b/docs/_includes/page-card.njk @@ -2,7 +2,7 @@
- {% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" %} + {% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
{{ page.data.title }} {% if pageSubtitle -%} diff --git a/docs/_includes/sidebar-group.njk b/docs/_includes/sidebar-group.njk index c49ef48da..b723d22ab 100644 --- a/docs/_includes/sidebar-group.njk +++ b/docs/_includes/sidebar-group.njk @@ -1,9 +1,12 @@ {# Some collections (like "patterns") will not have any items in the alpha build for example. So this checks to make sure the collection exists. #} {% if collections[tag] -%} {% set groupUrl %}/docs/{{ tag }}/{% endset %} + {% set groupItem = groupUrl | getCollectionItemFromUrl %} + {% set children = groupItem.data.children if groupItem.data.children.length > 0 else (collections[tag] | sort) %} +

- {% if groupUrl | getCollectionItemFromUrl %} + {% if groupItem %} {{ title or (tag | capitalize) }} @@ -12,10 +15,8 @@ {% endif %}

diff --git a/docs/_includes/sidebar-link.njk b/docs/_includes/sidebar-link.njk index dab3e32a0..d44628b07 100644 --- a/docs/_includes/sidebar-link.njk +++ b/docs/_includes/sidebar-link.njk @@ -1,4 +1,4 @@ -{% if not (isAlpha and page.data.noAlpha) and page.fileSlug != tag and not page.data.unlisted -%} +{% if page | show -%}
  • {{ page.data.title }} {% if page.data.status == 'experimental' %}{% endif %} diff --git a/docs/_includes/svgs/action-panel.njk b/docs/_includes/svgs/action-panel.njk new file mode 100644 index 000000000..514314c31 --- /dev/null +++ b/docs/_includes/svgs/action-panel.njk @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/_layouts/overview.njk b/docs/_layouts/overview.njk index fbc99b73d..48645d379 100644 --- a/docs/_layouts/overview.njk +++ b/docs/_layouts/overview.njk @@ -1,6 +1,5 @@ --- layout: page-outline -tags: ["overview"] --- {% set forTag = forTag or (page.url | split('/') | last) %} {% if description %} @@ -13,8 +12,10 @@ tags: ["overview"] -{% set allPages = collections[forTag] %} +{% set allPages = allPages or collections[forTag] %} +{% if allPages and allPages.length > 0 %} {% include "grouped-pages.njk" %} +{% endif %} diff --git a/docs/_layouts/page.njk b/docs/_layouts/page.njk index a55a02c21..203527a44 100644 --- a/docs/_layouts/page.njk +++ b/docs/_layouts/page.njk @@ -1,4 +1,9 @@ -{% set hasSidebar = true %} -{% set hasOutline = false %} +{% if hasSidebar == undefined %} + {% set hasSidebar = true %} +{% endif %} + +{% if hasOutline == undefined %} + {% set hasOutline = false %} +{% endif %} {% extends "../_includes/base.njk" %} diff --git a/docs/_layouts/theme.njk b/docs/_layouts/theme.njk index 97c737f41..63693354c 100644 --- a/docs/_layouts/theme.njk +++ b/docs/_layouts/theme.njk @@ -68,7 +68,7 @@ wa_data.palettes = {
    - {% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" %} + {% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
    {{ palette.data.title }} diff --git a/docs/_utils/filters.js b/docs/_utils/filters.js index 912b5dbb1..9747a78b3 100644 --- a/docs/_utils/filters.js +++ b/docs/_utils/filters.js @@ -29,6 +29,9 @@ function getCollection(name) { } export function getCollectionItemFromUrl(url, collection) { + if (!url) { + return null; + } collection ??= getCollection.call(this, 'all') || []; return collection.find(item => item.url === url); } @@ -42,35 +45,33 @@ export function split(text, separator) { return (text + '').split(separator).filter(Boolean); } -export function breadcrumbs(url, { withCurrent = false } = {}) { - const parts = split(url, '/'); - const ret = []; +export function ancestors(url, { withCurrent = false, withRoot = false } = {}) { + let ret = []; + let currentUrl = url; + let currentItem = getCollectionItemFromUrl.call(this, url); - while (parts.length) { - let partialUrl = '/' + parts.join('/') + '/'; - let item = getCollectionItemFromUrl.call(this, partialUrl); - - if (item && (partialUrl !== url || withCurrent)) { - let title = item.data.title; - if (title) { - ret.unshift({ url: partialUrl, title }); - } - } - - parts.pop(); - - if (item?.data.parent) { - let parentURL = item.data.parent; - if (!item.data.parent.startsWith('/')) { - // Parent is in the same directory - parts.push(item.data.parent); - parentURL = '/' + parts.join('/') + '/'; - } - - let parentBreadcrumbs = breadcrumbs.call(this, parentURL, { withCurrent: true }); - return [...parentBreadcrumbs, ...ret]; + if (!currentItem) { + // Might have eleventyExcludeFromCollections, jump to parent + let parentUrl = this.ctx.parentUrl; + if (parentUrl) { + url = parentUrl; } } + + for (let item; (item = getCollectionItemFromUrl.call(this, url)); url = item.data.parentUrl) { + ret.unshift(item); + } + + if (!withRoot && ret[0]?.page.url === '/') { + // Remove root + ret.shift(); + } + + if (!withCurrent && ret.at(-1)?.page.url === currentUrl) { + // Remove current page + ret.pop(); + } + return ret; } @@ -177,72 +178,196 @@ export function sort(arr, by = { 'data.order': 1, 'data.title': '' }) { }); } +export function show(page) { + return !(page.data.noAlpha && page.data.isAlpha) && !page.data.unlisted; +} + /** * Group an 11ty collection (or any array of objects with a `data.tags` property) by certain tags. * @param {object[]} collection - * @param { Object | (string | Object)[]} [tags] The tags to group by. If not provided/empty, defaults to grouping by all tags. - * @returns { Object. } An object with keys for each tag, and an array of items for each tag. + * @param { Object | string[]} [options] Options object or array of tags to group by. + * @param {string[] | true} [options.tags] Tags to group by. If true, groups by all tags. + * If not provided/empty, defaults to grouping by page hierarchy, with any pages with more than 1 children becoming groups. + * @param {string[]} [options.groups] The groups to use if only a subset or a specific order is desired. Defaults to `options.tags`. + * @param {string[]} [options.titles] Any title overrides for groups. + * @param {string | false} [options.other="Other"] The title to use for the "Other" group. If `false`, the "Other" group is removed.. + * @returns { Object. } An object of group ids to arrays of page objects. */ -export function groupByTags(collection, tags) { +export function groupPages(collection, options = {}, page) { if (!collection) { - console.error(`Empty collection passed to groupByTags() to group by ${JSON.stringify(tags)}`); - } - if (!tags) { - // Default to grouping by union of all tags - tags = Array.from(new Set(collection.flatMap(item => item.data.tags))); - } else if (Array.isArray(tags)) { - // May contain objects of one-off tag -> label mappings - tags = tags.map(tag => (typeof tag === 'object' ? Object.keys(tag)[0] : tag)); - } else if (typeof tags === 'object') { - // tags is an object of tags to labels, so we just want the keys - tags = Object.keys(tags); + console.error(`Empty collection passed to groupPages() to group by ${JSON.stringify(options)}`); } - let ret = Object.fromEntries(tags.map(tag => [tag, []])); - ret.other = []; + if (Array.isArray(options)) { + options = { tags: options }; + } + + let { tags, groups, titles = {}, other = 'Other', filter = show } = options; + + if (groups === undefined && Array.isArray(tags)) { + groups = tags; + } + + let grouping; + + if (tags) { + grouping = { + isGroup: item => undefined, + getCandidateGroups: item => item.data.tags, + getGroupMeta: group => ({}), + }; + } else { + grouping = { + isGroup: item => (item.data.children.length >= 2 ? item.page.url : undefined), + getCandidateGroups: item => { + let parentUrl = item.data.parentUrl; + if (page?.url === parentUrl) { + return []; + } + return [parentUrl]; + }, + getGroupMeta: group => { + let item = byUrl[group] || getCollectionItemFromUrl.call(this, group); + return { + title: item?.data.title, + url: group, + item, + }; + }, + sortGroups: groups => sort(groups.map(url => byUrl[url]).filter(Boolean)).map(item => item.page.url), + }; + } + + let byUrl = {}; + let byParentUrl = {}; + + if (filter) { + collection = collection.filter(filter); + } for (let item of collection) { - let categorized = false; + let url = item.page.url; + let parentUrl = item.data.parentUrl; - for (let tag of tags) { - if (item.data.tags.includes(tag)) { - ret[tag].push(item); - categorized = true; - } - } + byUrl[url] = item; - if (!categorized) { - ret.other.push(item); + if (parentUrl) { + byParentUrl[parentUrl] ??= []; + byParentUrl[parentUrl].push(item); } } - // Remove empty categories - for (let category in ret) { - if (ret[category].length === 0) { - delete ret[category]; + let urlToGroups = {}; + + for (let item of collection) { + let url = item.page.url; + let parentUrl = item.data.parentUrl; + + if (grouping.isGroup(item)) { + continue; + } + + let parentItem = byUrl[parentUrl]; + if (parentItem && !grouping.isGroup(parentItem)) { + // Their parent is also here and is not a group + continue; + } + + let candidateGroups = grouping.getCandidateGroups(item); + + if (groups) { + candidateGroups = candidateGroups.filter(group => groups.includes(group)); + } + + urlToGroups[url] ??= []; + + for (let group of candidateGroups) { + urlToGroups[url].push(group); + } + } + + let ret = {}; + + for (let url in urlToGroups) { + let groups = urlToGroups[url]; + let item = byUrl[url]; + + if (groups.length === 0) { + // Not filtered out but also not categorized + groups = ['other']; + } + + for (let group of groups) { + ret[group] ??= []; + ret[group].push(item); + + if (!ret[group].meta) { + if (group === 'other') { + ret[group].meta = { title: other }; + } else { + ret[group].meta = grouping.getGroupMeta(group); + ret[group].meta.title = titles[group] ?? ret[group].meta.title ?? capitalize(group); + } + } + } + } + + if (other === false) { + delete ret.other; + } + + // Sort + let sortedGroups = groups ?? grouping.sortGroups?.(Object.keys(ret)); + + if (sortedGroups) { + ret = sortObject(ret, sortedGroups); + } else { + // At least make sure other is last + if (ret.other) { + let otherGroup = ret.other; + delete ret.other; + ret.other = otherGroup; + } + } + + Object.defineProperty(ret, 'meta', { + value: { + groupCount: Object.keys(ret).length, + }, + enumerable: false, + }); + + return ret; +} + +/** + * Sort an object by its keys + * @param {*} obj + * @param {function | string[]} order + */ +function sortObject(obj, order) { + let ret = {}; + let sortedKeys = Array.isArray(order) ? order : Object.keys(obj).sort(order); + + for (let key of sortedKeys) { + if (key in obj) { + ret[key] = obj[key]; + } + } + + // Add any keys that weren't in the order + for (let key in obj) { + if (!(key in ret)) { + ret[key] = obj[key]; } } return ret; } -export function getCategoryTitle(category, categories) { - let title; - if (Array.isArray(categories)) { - // Find relevant entry - // [{id: "Title"}, id2, ...] - title = categories.find(entry => typeof entry === 'object' && entry?.[category])?.[category]; - } else if (typeof categories === 'object') { - // {id: "Title", id2: "Title 2", ...} - title = categories[category]; - } - - if (title) { - return title; - } - - // Capitalized - return category.charAt(0).toUpperCase() + category.slice(1); +function capitalize(str) { + str += ''; + return str.charAt(0).toUpperCase() + str.slice(1); } const IDENTITY = x => x; diff --git a/docs/_utils/outline.js b/docs/_utils/outline.js index 0281ee66b..026a59e00 100644 --- a/docs/_utils/outline.js +++ b/docs/_utils/outline.js @@ -39,7 +39,7 @@ export function outlinePlugin(options = {}) { } // Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content - clone.querySelectorAll('a').forEach(a => a.remove()); + clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove()); clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove()); // Generate the link diff --git a/docs/assets/scripts/filter.js b/docs/assets/scripts/filter.js index 7d0398f41..d0b16de9e 100644 --- a/docs/assets/scripts/filter.js +++ b/docs/assets/scripts/filter.js @@ -1,3 +1,11 @@ +function debounce(func, wait) { + let timeout; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; +} + function updateResults(input) { const filter = input.value.toLowerCase().trim(); let filtered = Boolean(filter); @@ -18,8 +26,10 @@ function updateResults(input) { } } +const debouncedUpdateResults = debounce(updateResults, 300); + document.documentElement.addEventListener('input', e => { if (e.target?.matches('#block-filter wa-input')) { - updateResults(e.target); + debouncedUpdateResults(e.target); } }); diff --git a/docs/assets/scripts/theme-picker.js b/docs/assets/scripts/theme-picker.js index e9220a235..f1d69e1e6 100644 --- a/docs/assets/scripts/theme-picker.js +++ b/docs/assets/scripts/theme-picker.js @@ -1,12 +1,30 @@ +let initialPageLoadComplete = false; + +window.addEventListener('load', () => { + initialPageLoadComplete = true; +}); + // Helper for view transitions -export function domChange(fn, { behavior = 'smooth' } = {}) { +export function domChange(fn, { behavior = 'smooth', ignoreInitialLoad = true } = {}) { const canUseViewTransitions = document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches; + // Skip transitions on initial page load + if (!initialPageLoadComplete && ignoreInitialLoad) { + fn(false); + return null; + } + if (canUseViewTransitions && behavior === 'smooth') { - document.startViewTransition(fn); + const transition = document.startViewTransition(() => { + fn(true); + // Wait a brief delay before finishing the transition to prevent jumpiness + return new Promise(resolve => setTimeout(resolve, 200)); + }); + return transition; } else { - fn(true); + fn(false); + return null; } } diff --git a/docs/assets/scripts/turbo.js b/docs/assets/scripts/turbo.js index fb1d0ea0f..e00e470c3 100644 --- a/docs/assets/scripts/turbo.js +++ b/docs/assets/scripts/turbo.js @@ -1,3 +1,6 @@ +import 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.10/+esm'; +import { preventTurboFouce } from '/dist/webawesome.js'; + if (!window.___turboScrollPositions___) { window.___turboScrollPositions___ = {}; } @@ -70,3 +73,4 @@ function fixDSD(e) { window.addEventListener('turbo:before-cache', saveScrollPosition); window.addEventListener('turbo:before-render', restoreScrollPosition); window.addEventListener('turbo:render', restoreScrollPosition); +preventTurboFouce(); diff --git a/docs/assets/styles/docs.css b/docs/assets/styles/docs.css index 3a275228f..d51d19360 100644 --- a/docs/assets/styles/docs.css +++ b/docs/assets/styles/docs.css @@ -370,10 +370,22 @@ wa-page > main:has(> .index-grid) { .index-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(22ch, 100%), 1fr)); + grid-template-columns: repeat(4, 1fr); gap: var(--wa-space-2xl); margin-block-end: var(--wa-space-3xl); + @media screen and (max-width: 1470px) { + grid-template-columns: repeat(3, 1fr); + } + + @media screen and (max-width: 960px) { + grid-template-columns: repeat(2, 1fr); + } + + @media screen and (max-width: 500px) { + grid-template-columns: repeat(1, 1fr); + } + a { border-radius: var(--wa-border-radius-l); text-decoration: none; diff --git a/docs/docs/components/card.md b/docs/docs/components/card.md index d1cd32a64..daf427d79 100644 --- a/docs/docs/components/card.md +++ b/docs/docs/components/card.md @@ -15,9 +15,9 @@ icon: card Mittens
    This kitten is as cute as he is playful. Bring him home today!
    - 6 weeks old + -
    + @@ -27,16 +27,6 @@ icon: card .card-overview { width: 300px; } - - .card-overview small { - color: var(--wa-color-text-quiet); - } - - .card-overview [slot='footer'] { - display: flex; - justify-content: space-between; - align-items: center; - } ``` @@ -65,9 +55,9 @@ If using SSR, you need to also use the `with-header` attribute to add a header t ```html {.example} -
    + This card has a header. You can put all sorts of things in it! @@ -78,19 +68,9 @@ If using SSR, you need to also use the `with-header` attribute to add a header t max-width: 300px; } - .card-header [slot='header'] { - display: flex; - align-items: center; - justify-content: space-between; - } - .card-header h3 { margin: 0; } - - .card-header wa-icon-button { - font-size: var(--wa-font-size-m); - } ``` @@ -103,7 +83,7 @@ If using SSR, you need to also use the `with-footer` attribute to add a footer t This card has a footer. You can put all sorts of things in it! -
    + @@ -113,12 +93,6 @@ If using SSR, you need to also use the `with-footer` attribute to add a footer t .card-footer { max-width: 300px; } - - .card-footer [slot='footer'] { - display: flex; - justify-content: space-between; - align-items: center; - } ``` @@ -153,7 +127,7 @@ Use the `size` attribute to change a card's size. This is a small card. -
    +
    More Info
    @@ -162,7 +136,7 @@ Use the `size` attribute to change a card's size. This is a medium card (default). -
    +
    More Info
    @@ -171,14 +145,39 @@ Use the `size` attribute to change a card's size. This is a large card. -
    +
    More Info
    - ``` - +### Appearance + +Use the `appearance` attribute to change the card's visual appearance. + +```html {.example} + +``` diff --git a/docs/docs/components/details.md b/docs/docs/components/details.md index 07cd53b33..9f6d94107 100644 --- a/docs/docs/components/details.md +++ b/docs/docs/components/details.md @@ -77,6 +77,31 @@ The details component automatically adapts to right-to-left languages: ``` +### Appearance + +Use the `appearance` attribute to change the element’s visual appearance. + +```html {.example} + +``` + ### 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. diff --git a/docs/docs/components/index.njk b/docs/docs/components/index.njk index f5d19614d..64ddf6e40 100644 --- a/docs/docs/components/index.njk +++ b/docs/docs/components/index.njk @@ -2,13 +2,10 @@ title: Components description: Components are the essential building blocks to create intuitive, cohesive experiences. Browse the library of customizable, framework-friendly web components included in Web Awesome. layout: overview -categories: - - actions - - feedback: 'Feedback & Status' - - imagery - - inputs - - navigation - - organization - - helpers: 'Utilities' override:tags: [] +categories: + tags: [actions, feedback, imagery, inputs, navigation, organization, helpers] + titles: + feedback: 'Feedback & Status' + helpers: 'Utilities' --- diff --git a/docs/docs/docs.11tydata.js b/docs/docs/docs.11tydata.js index 7ed1011e1..2232f7e53 100644 --- a/docs/docs/docs.11tydata.js +++ b/docs/docs/docs.11tydata.js @@ -1,10 +1,80 @@ +/** + * Global data for all pages + */ +import { sort } from '../_utils/filters.js'; + export default { eleventyComputed: { - children(data) { - let mainTag = data.tags?.[0]; - let collection = data.collections[mainTag] ?? []; + // Default parent. Can be overridden by explicitly setting parent in the data. + // parent can refer to either an ancestor page in the URL or another page in the same directory + parent(data) { + let { parent, page } = data; - return collection.filter(item => item.data.parent === data.page.fileSlug); + if (parent) { + return parent; + } + + return page.url.split('/').filter(Boolean).at(-2); + }, + + parentUrl(data) { + let { parent, page } = data; + return getParentUrl(page.url, parent); + }, + + parentItem(data) { + let { parentUrl } = data; + return data.collections.all.find(item => item.url === parentUrl); + }, + + children(data) { + let { collections, page, parentOf } = data; + + if (parentOf) { + return collections[parentOf]; + } + + let collection = collections.all ?? []; + let url = page.url; + + let ret = collection.filter(item => { + return item.data.parentUrl === url; + }); + + sort(ret); + + return ret; }, }, }; + +function getParentUrl(url, parent) { + let parts = url.split('/').filter(Boolean); + let ancestorIndex = parts.findLastIndex(part => part === parent); + let retParts = parts.slice(); + + if (ancestorIndex > -1) { + // parent is an ancestor + retParts.splice(ancestorIndex + 1); + } else { + // parent is a sibling in the same directory + retParts.splice(-1, 1, parent); + } + + let ret = retParts.join('/'); + + if (url.startsWith('/')) { + ret = '/' + ret; + } + + if (!retParts.at(-1).includes('.') && !ret.endsWith('/')) { + // If no extension, make sure to end with a slash + ret += '/'; + } + + if (ret === '/docs/') { + ret = '/'; + } + + return ret; +} diff --git a/docs/docs/installation.md b/docs/docs/installation.md index 473e4ab1f..b142497bc 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -37,10 +37,6 @@ This snippet includes three parts: Now you can [start using Web Awesome!](/docs/usage) -:::info -While convenient, autoloading may lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/). The linked article describes some ways to alleviate it. -::: - --- ## Using Font Awesome Kit Codes diff --git a/docs/docs/layout.njk b/docs/docs/layout.njk index 67d8f8778..57af8bc94 100644 --- a/docs/docs/layout.njk +++ b/docs/docs/layout.njk @@ -2,6 +2,7 @@ title: Layout description: Layout components and utility classes help you organize content that can adapt to any device or screen size. See the [installation instructions](#installation) to use Web Awesome's layout tools in your project. layout: overview +parentOf: layout categories: ["components", "utilities"] override:tags: [] --- @@ -22,4 +23,4 @@ Or, you can choose to import _only_ the utilities: ```html ``` -{% endmarkdown %} \ No newline at end of file +{% endmarkdown %} diff --git a/docs/docs/native/button.md b/docs/docs/native/button.md index b9740a711..3c050199f 100644 --- a/docs/docs/native/button.md +++ b/docs/docs/native/button.md @@ -33,7 +33,7 @@ Use the [variant utility classes](../utilities/color.md) to set the button's sem ### Appearance -Use the [appearance utility classes](../utilities/appearance.md) to change the button's visual appearance: +Use the [appearance utility classes](/docs/utilities/appearance) to change the button's visual appearance: ```html {.example}
    diff --git a/docs/docs/native/callout.md b/docs/docs/native/callout.md index e3ef62b88..ae8a5cdbf 100644 --- a/docs/docs/native/callout.md +++ b/docs/docs/native/callout.md @@ -57,7 +57,7 @@ Use the [variant utility classes](../utilities/color.md) to set the callout's co ### Appearance -Use the [appearance utility classes](../utilities/appearance.md) to change the callout's visual appearance (the default is `outlined filled`). +Use the [appearance utility classes](/docs/utilities/appearance) to change the callout's visual appearance (the default is `outlined filled`). ```html {.example}
    -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/app.json b/docs/docs/patterns/app/app.json new file mode 100644 index 000000000..ec69525f8 --- /dev/null +++ b/docs/docs/patterns/app/app.json @@ -0,0 +1,3 @@ +{ + "tags": ["app"] +} diff --git a/docs/docs/patterns/app/comments.md b/docs/docs/patterns/app/comments.md index abc7ea673..2f0522859 100644 --- a/docs/docs/patterns/app/comments.md +++ b/docs/docs/patterns/app/comments.md @@ -1,8 +1,6 @@ --- title: Comments description: 'For feedback forms and message boxes' -parent: app -tags: app --- ## Examples @@ -34,7 +32,7 @@ tags: app
    - Comment + Comment
    @@ -72,4 +70,4 @@ tags: app Save
    -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/data-display.md b/docs/docs/patterns/app/data-display.md index 082b3d34b..6e722e0e2 100644 --- a/docs/docs/patterns/app/data-display.md +++ b/docs/docs/patterns/app/data-display.md @@ -1,8 +1,6 @@ --- title: Data Display description: TODO -parent: app -tags: app --- ## Examples @@ -27,13 +25,13 @@ tags: app @@ -41,13 +39,13 @@ tags: app @@ -71,11 +69,11 @@ tags: app @@ -105,7 +103,6 @@ tags: app -$5.00 - @@ -158,4 +155,4 @@ tags: app -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/description-list.md b/docs/docs/patterns/app/description-list.md index 8736a671f..3593d4c16 100644 --- a/docs/docs/patterns/app/description-list.md +++ b/docs/docs/patterns/app/description-list.md @@ -1,8 +1,6 @@ --- title: Description List description: 'Shows the user information with labels and values in an easy to read format.' -parent: app -tags: app --- ## Examples @@ -241,4 +239,4 @@ tags: app -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/empty-state.md b/docs/docs/patterns/app/empty-state.md index 820d48c4e..61d757ce5 100644 --- a/docs/docs/patterns/app/empty-state.md +++ b/docs/docs/patterns/app/empty-state.md @@ -1,8 +1,6 @@ --- title: Empty State description: TODO -parent: app -tags: app --- ## Examples @@ -110,4 +108,4 @@ tags: app Or start from an empty project → -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/faq.md b/docs/docs/patterns/app/faq.md index fe417b329..1d4b00c74 100644 --- a/docs/docs/patterns/app/faq.md +++ b/docs/docs/patterns/app/faq.md @@ -1,10 +1,10 @@ --- title: FAQ -description: TODO -parent: app -tags: app +description: 'The user has questions concerning a site and its related services' --- +## Examples +### Offset ```html{.example} ``` - +### With detail component ```html{.example}

    Frequently Asked Questions

    ``` - +### 2 Column ```html{.example}
    @@ -64,29 +88,36 @@ tags: app
    -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/feed.md b/docs/docs/patterns/app/feed.md index 3d9ada8d0..787363390 100644 --- a/docs/docs/patterns/app/feed.md +++ b/docs/docs/patterns/app/feed.md @@ -1,86 +1,157 @@ --- title: Feed description: TODO -parent: app -tags: app --- -```html {.example} -
    -
    - -
    - -

    Kicked ass and chewed bubblegum

    - Oct. 31st +## Comment Section +```html{.example} + + -
    - -
    - -

    Kicked ass and chewed bubblegum

    - Oct. 31st + +``` +## With Summary Components +```html{.example} + +

    Monthly Activity

    +
    -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/pagination.md b/docs/docs/patterns/app/pagination.md index fa6291cbc..6b597457f 100644 --- a/docs/docs/patterns/app/pagination.md +++ b/docs/docs/patterns/app/pagination.md @@ -1,8 +1,6 @@ --- title: Pagination description: TODO -parent: app -tags: app --- ## Simple Pagination @@ -41,4 +39,4 @@ tags: app Next
    -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/app/pricing.md b/docs/docs/patterns/app/pricing.md index 995dd9127..7f34936fa 100644 --- a/docs/docs/patterns/app/pricing.md +++ b/docs/docs/patterns/app/pricing.md @@ -1,8 +1,6 @@ --- title: Pricing description: TODO -parent: app -tags: app --- ```html{.example} @@ -29,23 +27,23 @@ tags: app
    @@ -72,7 +70,7 @@ tags: app
    @@ -99,7 +97,7 @@ tags: app
    @@ -134,4 +132,4 @@ tags: app ### With templates -### With recommendations grid \ No newline at end of file +### With recommendations grid diff --git a/docs/docs/patterns/ecommerce/category-filter.md b/docs/docs/patterns/ecommerce/category-filter.md index 7df961f8f..081450020 100644 --- a/docs/docs/patterns/ecommerce/category-filter.md +++ b/docs/docs/patterns/ecommerce/category-filter.md @@ -1,8 +1,6 @@ --- title: Category Filter description: 'Helps the user find the right products with filters to refine search results by specific attributes.' -parent: ecommerce -tags: e-commerce icon: checkbox --- @@ -42,7 +40,7 @@ icon: checkbox XXL - + @@ -82,4 +80,4 @@ icon: checkbox -``` \ No newline at end of file +``` diff --git a/docs/docs/patterns/ecommerce/category-preview.md b/docs/docs/patterns/ecommerce/category-preview.md index 721789cdf..9efeb7cb5 100644 --- a/docs/docs/patterns/ecommerce/category-preview.md +++ b/docs/docs/patterns/ecommerce/category-preview.md @@ -1,8 +1,6 @@ --- title: Category Preview description: 'Help shoppers discover your product offerings with showcases of product categories.' -parent: ecommerce -tags: e-commerce icon: preview --- @@ -17,20 +15,20 @@ icon: preview
    - -
    - {% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" %} -
    - {{ page.data.title }} - {% if pageSubtitle -%} - - {%- endif %} -
    -
    - - {%- endfor -%} - -
    diff --git a/docs/docs/patterns/ecommerce/order-history.md b/docs/docs/patterns/ecommerce/order-history.md index a12e51e69..a889d9c66 100644 --- a/docs/docs/patterns/ecommerce/order-history.md +++ b/docs/docs/patterns/ecommerce/order-history.md @@ -1,8 +1,6 @@ --- title: Order History description: 'Empower your customers to view past purchases and track upcoming orders with comprehensive order histories.' -parent: ecommerce -tags: e-commerce --- ## List @@ -32,7 +30,7 @@ tags: e-commerce + diff --git a/docs/docs/patterns/ecommerce/order-summary.md b/docs/docs/patterns/ecommerce/order-summary.md index e2bd0721d..566e1ca31 100644 --- a/docs/docs/patterns/ecommerce/order-summary.md +++ b/docs/docs/patterns/ecommerce/order-summary.md @@ -1,8 +1,6 @@ --- title: Order Summary description: 'Give shoppers confidence in their purchases with summaries of everything included in their order.' -parent: ecommerce -tags: e-commerce --- ## Simple @@ -26,7 +24,7 @@ tags: e-commerce