diff --git a/blocks/cdn-path/cdn-path.css b/blocks/cdn-path/cdn-path.css new file mode 100644 index 00000000..b60527dd --- /dev/null +++ b/blocks/cdn-path/cdn-path.css @@ -0,0 +1,120 @@ +.cdn-path form { + display: grid; + gap: 1rem; +} + +.cdn-path label { + position: relative; + display: flex; + align-items: center; + gap: 1rem; + border-radius: var(--image-border-radius-m); + padding: 1rem; + padding-left: calc((2 * 1rem) + var(--circular-icon-tag-size)); + cursor: pointer; + user-select: none; + transition: border-radius 2s, background-color .2s; +} + +@media (min-width: 900px) { + .cdn-path label { + min-height: calc((2 * 1rem) + var(--circular-icon-tag-size)); + } +} + +.cdn-path label:hover { + background-color: var(--bg-color-lightgrey); +} + +.cdn-path input, +.cdn-path span { + display: block; + position: absolute; + top: 50%; + left: 1rem; + transform: translateY(-50%); + margin: 0; + width: var(--circular-icon-tag-size); + height: var(--circular-icon-tag-size); + border-radius: 50%; +} + +.cdn-path span, +.cdn-path span::after { + transition: background-color .2s, opacity .2s; +} + +.cdn-path span::after { + content: ""; + position: absolute; + top: 7px; + left: 11px; + display: block; + width: 5px; + height: 10px; + transform: rotate(45deg); + border: solid var(--color-accent-lightgreen-content); + border-width: 0 3px 3px 0; + opacity: 0; + transition: background-color .4s, opacity .4s; +} + +@media (min-width: 900px) { + .cdn-path span::after { + top: 10px; + left: 19px; + width: 10px; + height: 20px; + border-width: 0 4px 4px 0; + } +} + +.cdn-path input { + appearance: none; + opacity: 0; +} + +.cdn-path span { + box-sizing: border-box; + background-color: var(--bg-color-grey); +} + +.cdn-path input:checked ~ span { + background-color: var(--color-accent-lightgreen-bg); +} + +.cdn-path input:checked ~ span::after { + opacity: 1; +} + +.cdn-path label:hover input ~ span::after { + opacity: .5; +} + +.cdn-path button { + display: inline-block; + width: max-content; + height: fit-content; + margin: 0 auto; + padding: 5px 1.2em 6px; + border: 2px solid; + border-color: var(--spectrum-blue); + border-radius: var(--image-border-radius-xxl); + background-color: var(--spectrum-blue); + color: var(--color-white); + font-family: inherit; + font-size: var(--type-body-s-size); + font-weight: 600; + line-height: var(--body-line-height); + text-align: center; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + cursor: pointer; + transition: background-color 0.3s, color 0.3s +} + +.cdn-path button:hover { + background-color: var(--dark-spectrum-blue); + border-color: var(--dark-spectrum-blue); +} diff --git a/blocks/cdn-path/cdn-path.js b/blocks/cdn-path/cdn-path.js new file mode 100644 index 00000000..d05ee4fb --- /dev/null +++ b/blocks/cdn-path/cdn-path.js @@ -0,0 +1,92 @@ +import { createTag } from '../../scripts/scripts.js'; +import { toClassName } from '../../scripts/lib-franklin.js'; + +function toCamelCase(string) { + return toClassName(string).replace(/-([a-z])/g, (s) => s[1].toUpperCase()); +} + +async function fetchQuestionnaire(source) { + const { pathname } = new URL(source); + if (pathname) { + const req = await fetch(pathname); + const res = await req.json(); + const questionnaire = { questions: res.data }; + if (questionnaire.questions.length) { + questionnaire.choices = Object.keys(res.data[0]).slice(1); + } + return questionnaire; + } + return {}; +} + +function buildQuestion(q) { + const name = toClassName(q.Question); + const label = createTag('label', { for: name }, q.Question); + const input = createTag('input', { type: 'checkbox', name, id: name }); + Object.keys(q).forEach((choice) => { + // eslint-disable-next-line eqeqeq + if (q[choice] == parseInt(q[choice], 10)) { + input.dataset[toCamelCase(choice)] = q[choice]; + } + }); + const span = createTag('span', { class: 'checkbox' }); + label.append(input, span); + return label; +} + +function findPaths(e) { + const checked = e.target.querySelectorAll('input:checked'); + const results = {}; + checked.forEach((check) => { + Object.keys(check.dataset).forEach((choice) => { + const value = parseInt(check.dataset[choice], 10); + if (results[choice]) { + results[choice] += value; + } else { + results[choice] = value; + } + }); + }); + const sorted = Object.entries(results).sort((a, b) => b[1] - a[1]); + const topMatches = []; + let count = 0; + while (count < 3 && sorted[0] && sorted[0][1] && sorted[0][1] > 0) { + const [choice] = sorted; + topMatches.push(choice); + sorted.shift(); + count += 1; + } + return topMatches; +} + +function displayPaths(e, el, choices) { + e.preventDefault(); + el.innerHTML = ''; + const paths = findPaths(e); + paths.forEach((path) => { + const choice = choices.find((c) => toCamelCase(c) === path[0]); + const li = createTag('li', {}, choice); + // TODO: actually present results in a useful way + el.append(li); + }); +} + +export default async function decorate(block) { + const source = block.querySelector('a[href]'); + block.innerHTML = ''; + if (source) { + const data = await fetchQuestionnaire(source); + // build questionnaire + const form = createTag('form'); + const results = createTag('ol', { class: 'cdn-path-results' }); + form.addEventListener('submit', (e) => displayPaths(e, results, data.choices)); + data.questions.forEach((d) => { + const q = buildQuestion(d); + form.append(q); + }); + // TODO: move string out of code + const button = createTag('button', { type: 'submit' }, 'Find Your Path'); + form.append(button); + block.append(form, results); + } +}