From da1be2cc4aaef1a020247d29f269473aa2f1d35c Mon Sep 17 00:00:00 2001 From: npy11 Date: Wed, 18 Sep 2024 12:32:39 +0530 Subject: [PATCH] form js css added and parternship css done --- blocks/form/form-fields.js | 237 +++++++++++++++++++++++++++++ blocks/form/form.css | 174 +++++++++++++++++++++ blocks/form/form.js | 102 +++++++++++++ blocks/form/updatesheet-form.css | 133 ++++++++++++++++ styles/partnership/partnership.css | 10 ++ 5 files changed, 656 insertions(+) create mode 100644 blocks/form/form-fields.js create mode 100644 blocks/form/form.css create mode 100644 blocks/form/form.js create mode 100644 blocks/form/updatesheet-form.css diff --git a/blocks/form/form-fields.js b/blocks/form/form-fields.js new file mode 100644 index 0000000000..2a1290c114 --- /dev/null +++ b/blocks/form/form-fields.js @@ -0,0 +1,237 @@ + +import { toClassName } from '../../scripts/aem.js'; + +function createFieldWrapper(fd) { + const fieldWrapper = document.createElement('div'); + if (fd.Style) fieldWrapper.className = fd.Style; + fieldWrapper.classList.add('field-wrapper', `${fd.Type}-wrapper`); + + fieldWrapper.dataset.fieldset = fd.Fieldset; + + return fieldWrapper; +} + +const ids = []; +function generateFieldId(fd, suffix = '') { + const slug = toClassName(`form-${fd.Name}${suffix}`); + ids[slug] = ids[slug] || 0; + const idSuffix = ids[slug] ? `-${ids[slug]}` : ''; + ids[slug] += 1; + return `${slug}${idSuffix}`; +} + +function createLabel(fd) { + const label = document.createElement('label'); + label.id = generateFieldId(fd, '-label'); + label.textContent = fd.Label || fd.Name; + label.setAttribute('for', fd.Id); + if (fd.Mandatory.toLowerCase() === 'true' || fd.Mandatory.toLowerCase() === 'x') { + label.dataset.required = true; + } + return label; +} + +function setCommonAttributes(field, fd) { + field.id = fd.Id; + field.name = fd.Name; + field.required = fd.Mandatory && (fd.Mandatory.toLowerCase() === 'true' || fd.Mandatory.toLowerCase() === 'x'); + field.placeholder = fd.Placeholder; + field.value = fd.Value; +} + +const createHeading = (fd) => { + const fieldWrapper = createFieldWrapper(fd); + + const level = fd.Style && fd.Style.includes('sub-heading') ? 3 : 2; + const heading = document.createElement(`h${level}`); + heading.textContent = fd.Value || fd.Label; + heading.id = fd.Id; + + fieldWrapper.append(heading); + + return { field: heading, fieldWrapper }; +}; + +const createPlaintext = (fd) => { + const fieldWrapper = createFieldWrapper(fd); + + const text = document.createElement('p'); + text.textContent = fd.Value || fd.Label; + text.id = fd.Id; + + fieldWrapper.append(text); + + return { field: text, fieldWrapper }; +}; + +const createSelect = async (fd) => { + const select = document.createElement('select'); + setCommonAttributes(select, fd); + const addOption = ({ text, value }) => { + const option = document.createElement('option'); + option.text = text.trim(); + option.value = value.trim(); + if (option.value === select.value) { + option.setAttribute('selected', ''); + } + select.add(option); + return option; + }; + + if (fd.Placeholder) { + const ph = addOption({ text: fd.Placeholder, value: '' }); + ph.setAttribute('disabled', ''); + } + + if (fd.Options) { + let options = []; + if (fd.Options.startsWith('https://')) { + const optionsUrl = new URL(fd.Options); + const resp = await fetch(`${optionsUrl.pathname}${optionsUrl.search}`); + const json = await resp.json(); + json.data.forEach((opt) => { + options.push({ + text: opt.Option, + value: opt.Value || opt.Option, + }); + }); + } else { + options = fd.Options.split(',').map((opt) => ({ + text: opt.trim(), + value: opt.trim().toLowerCase(), + })); + } + + options.forEach((opt) => addOption(opt)); + } + + const fieldWrapper = createFieldWrapper(fd); + fieldWrapper.append(select); + fieldWrapper.prepend(createLabel(fd)); + + return { field: select, fieldWrapper }; +}; + +const createConfirmation = (fd, form) => { + form.dataset.confirmation = new URL(fd.Value).pathname; + + return {}; +}; + +const createSubmit = (fd) => { + const button = document.createElement('button'); + button.textContent = fd.Label || fd.Name; + button.classList.add('button'); + button.type = 'submit'; + + const fieldWrapper = createFieldWrapper(fd); + fieldWrapper.append(button); + return { field: button, fieldWrapper }; +}; + +const createTextArea = (fd) => { + const field = document.createElement('textarea'); + setCommonAttributes(field, fd); + + const fieldWrapper = createFieldWrapper(fd); + const label = createLabel(fd); + field.setAttribute('aria-labelledby', label.id); + fieldWrapper.append(field); + fieldWrapper.prepend(label); + + return { field, fieldWrapper }; +}; + +const createInput = (fd) => { + const field = document.createElement('input'); + field.type = fd.Type; + setCommonAttributes(field, fd); + + const fieldWrapper = createFieldWrapper(fd); + const label = createLabel(fd); + field.setAttribute('aria-labelledby', label.id); + fieldWrapper.append(field); + if (fd.Type === 'radio' || fd.Type === 'checkbox') { + fieldWrapper.append(label); + } else { + fieldWrapper.prepend(label); + } + + return { field, fieldWrapper }; +}; + +const createFieldset = (fd) => { + const field = document.createElement('fieldset'); + setCommonAttributes(field, fd); + + if (fd.Label) { + const legend = document.createElement('legend'); + legend.textContent = fd.Label; + field.append(legend); + } + + const fieldWrapper = createFieldWrapper(fd); + fieldWrapper.append(field); + + return { field, fieldWrapper }; +}; + +const createToggle = (fd) => { + const { field, fieldWrapper } = createInput(fd); + field.type = 'checkbox'; + if (!field.value) field.value = 'on'; + field.classList.add('toggle'); + fieldWrapper.classList.add('selection-wrapper'); + + const toggleSwitch = document.createElement('div'); + toggleSwitch.classList.add('switch'); + toggleSwitch.append(field); + fieldWrapper.append(toggleSwitch); + + const slider = document.createElement('span'); + slider.classList.add('slider'); + toggleSwitch.append(slider); + slider.addEventListener('click', () => { + field.checked = !field.checked; + }); + + return { field, fieldWrapper }; +}; + +const createCheckbox = (fd) => { + const { field, fieldWrapper } = createInput(fd); + if (!field.value) field.value = 'checked'; + fieldWrapper.classList.add('selection-wrapper'); + + return { field, fieldWrapper }; +}; + +const createRadio = (fd) => { + const { field, fieldWrapper } = createInput(fd); + if (!field.value) field.value = fd.Label || 'on'; + fieldWrapper.classList.add('selection-wrapper'); + + return { field, fieldWrapper }; +}; + +const FIELD_CREATOR_FUNCTIONS = { + select: createSelect, + heading: createHeading, + plaintext: createPlaintext, + 'text-area': createTextArea, + toggle: createToggle, + submit: createSubmit, + confirmation: createConfirmation, + fieldset: createFieldset, + checkbox: createCheckbox, + radio: createRadio, +}; + +export default async function createField(fd, form) { + fd.Id = fd.Id || generateFieldId(fd); + const type = fd.Type.toLowerCase(); + const createFieldFunc = FIELD_CREATOR_FUNCTIONS[type] || createInput; + const fieldElements = await createFieldFunc(fd, form); + + return fieldElements.fieldWrapper; +} diff --git a/blocks/form/form.css b/blocks/form/form.css new file mode 100644 index 0000000000..c385f80e2f --- /dev/null +++ b/blocks/form/form.css @@ -0,0 +1,174 @@ +@import url('/blocks/form/updatesheet-form.css'); + +.form .field-wrapper { + display: grid; + grid-auto-flow: row; + align-items: center; +} + +.form fieldset { + display: grid; + grid-auto-flow: row; + margin: 0; + border: none; + padding: 0; +} + +.form form>.field-wrapper+.field-wrapper, +.form form fieldset .field-wrapper+.field-wrapper { + margin-top: 24px; +} + +.form form>.selection-wrapper+.selection-wrapper, +.form form fieldset .selection-wrapper+.selection-wrapper { + margin-top: 0.25em; +} + +@media (width >=600px) { + .form fieldset { + grid-template-columns: repeat(2, auto); + gap: 0.25em 24px; + } + + .form form>.selection-wrapper+.selection-wrapper, + .form form fieldset .field-wrapper+.field-wrapper, + .form form fieldset .selection-wrapper+.selection-wrapper { + margin-top: 0; + } +} + +@media (width >=900px) { + .form fieldset { + grid-template-columns: repeat(3, auto); + } +} + +.form label, +.form fieldset>legend { + margin-bottom: 0.25em; + font-size: var(--body-font-size-s); + font-weight: 700; +} + +.form .selection-wrapper label { + margin: 0; + font-weight: normal; +} + +.form input, +.form select, +.form textarea { + box-sizing: border-box; + display: block; + width: 100%; + margin: 0; + padding: 0.5em; + border-radius: 4px; + border: 1px solid var(--dark-color); + background-color: var(--background-color); + color: var(--text-color); + font-size: var(--body-font-size-s); + transition: border-color 0.2s; +} + +.form textarea { + resize: vertical; +} + +.form input:hover, +.form select:hover, +.form textarea:hover { + border: 1px solid var(--text-color); +} + +.form input:focus, +.form select:focus, +.form textarea:focus { + outline: 2px solid var(--link-color); + outline-offset: 2px; +} + +.form .selection-wrapper input { + width: max-content; +} + +@media (width >=600px) { + + .form input, + .form select, + .form textarea { + max-width: 50vw; + } + + .form .button { + max-width: max-content; + } +} + +@media (width >=900px) { + + .form input, + .form select, + .form textarea { + max-width: 33vw; + } +} + +.form .field-wrapper.selection-wrapper { + grid-auto-flow: column; + justify-content: start; + gap: 1ch; +} + +.form label[data-required]::after { + content: '*'; + color: firebrick; + margin-inline-start: 1ch; +} + +.form .toggle-wrapper .switch { + position: relative; + display: inline-block; + width: 52px; + height: 28px; +} + +.form .toggle-wrapper input { + opacity: 0; + width: 52px; + height: 28px; +} + +.form .toggle-wrapper .slider { + position: absolute; + cursor: pointer; + inset: 0; + border-radius: 28px; + background-color: var(--dark-color); + transition: background-color 0.2s; +} + +.form .toggle-wrapper .slider::before { + content: ''; + position: absolute; + width: 24px; + height: 24px; + top: 2px; + left: 2px; + border-radius: 50%; + background-color: var(--background-color); + transition: transform 0.2s; +} + +.form .toggle-wrapper input:checked+.slider { + background-color: var(--link-color); +} + +.form .toggle-wrapper input:focus+.slider { + outline: 2px solid var(--link-color); + outline-offset: 2px; +} + +.form .toggle-wrapper input:checked+.slider::before { + transform: translateX(24px); +} \ No newline at end of file diff --git a/blocks/form/form.js b/blocks/form/form.js new file mode 100644 index 0000000000..6518430e05 --- /dev/null +++ b/blocks/form/form.js @@ -0,0 +1,102 @@ +import createField from './form-fields.js'; + +async function createForm(formHref, submitHref) { + const { pathname } = new URL(formHref); + const resp = await fetch(pathname); + const json = await resp.json(); + + const form = document.createElement('form'); + form.dataset.action = submitHref; + + const fields = await Promise.all(json.data.map((fd) => createField(fd, form))); + fields.forEach((field) => { + if (field) { + form.append(field); + } + }); + + // group fields into fieldsets + const fieldsets = form.querySelectorAll('fieldset'); + fieldsets.forEach((fieldset) => { + form.querySelectorAll(`[data-fieldset="${fieldset.name}"`).forEach((field) => { + fieldset.append(field); + }); + }); + + return form; +} + +function generatePayload(form) { + const payload = {}; + + [...form.elements].forEach((field) => { + if (field.name && field.type !== 'submit' && !field.disabled) { + if (field.type === 'radio') { + if (field.checked) payload[field.name] = field.value; + } else if (field.type === 'checkbox') { + if (field.checked) payload[field.name] = payload[field.name] ? `${payload[field.name]},${field.value}` : field.value; + } else { + payload[field.name] = field.value; + } + } + }); + return payload; +} + +async function handleSubmit(form) { + if (form.getAttribute('data-submitting') === 'true') return; + + const submit = form.querySelector('button[type="submit"]'); + try { + form.setAttribute('data-submitting', 'true'); + submit.disabled = true; + + // create payload + const payload = generatePayload(form); + const response = await fetch(form.dataset.action, { + method: 'POST', + body: JSON.stringify({ data: payload }), + headers: { + 'Content-Type': 'application/json', + }, + }); + if (response.ok) { + if (form.dataset.confirmation) { + window.location.href = form.dataset.confirmation; + } + } else { + const error = await response.text(); + throw new Error(error); + } + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } finally { + form.setAttribute('data-submitting', 'false'); + submit.disabled = false; + } +} + +export default async function decorate(block) { + const links = [...block.querySelectorAll('a')].map((a) => a.innerText); + const formLink = links.find((link) => link.startsWith(window.location.origin) && link.endsWith('.json')); + const submitLink = links.find((link) => link !== formLink); + // if (!formLink || !submitLink) return; + + const form = await createForm(formLink, submitLink); + block.replaceChildren(form); + + form.addEventListener('submit', (e) => { + e.preventDefault(); + const valid = form.checkValidity(); + if (valid) { + handleSubmit(form); + } else { + const firstInvalidEl = form.querySelector(':invalid:not(fieldset)'); + if (firstInvalidEl) { + firstInvalidEl.focus(); + firstInvalidEl.scrollIntoView({ behavior: 'smooth' }); + } + } + }); +} \ No newline at end of file diff --git a/blocks/form/updatesheet-form.css b/blocks/form/updatesheet-form.css new file mode 100644 index 0000000000..77a91cbcfa --- /dev/null +++ b/blocks/form/updatesheet-form.css @@ -0,0 +1,133 @@ +.update-sheet-form{ + padding: 60px 0; +} + +.update-sheet-form .form-wrapper .form{ + display: flex; + justify-content: center; +} + +.update-sheet-form .form-wrapper .form form{ + width: 50%; + padding: 16px 20px; + border-radius: 10px; + background-color: #f4f4f4; + border: 1px solid var(--dark-color); +} + +.update-sheet-form .form-wrapper .form form > div:nth-child(1){ + text-align: center; +} + +.update-sheet-form .form-wrapper .form form > div:nth-child(1) h2{ + font-size: 2rem; + font-weight: 700; + color: var(--orange); + font-family: "Nunito-Bold"; +} + +.update-sheet-form .form-wrapper .form form > div label{ + font-weight: 500; + margin-bottom: 1rem; + font-size: 1.125rem; + color: var(--orange); + font-family: "Nunito-Medium"; +} + +.section.update-sheet-form .form-wrapper .form form > div:nth-child(2) input, +.section.update-sheet-form .form-wrapper .form form > div:nth-child(3) select{ + width: 100%; + font-size: 18px; + cursor: pointer; + font-weight: 500; + font-family: "Nunito-Medium"; +} + +.update-sheet-form .form-wrapper .form form > div:nth-child(3) select option{ + width: inherit; + font-weight: 500; + padding-top: 10px; + font-family: "Nunito-Medium"; +} + +.update-sheet-form .form-wrapper .form form > div:last-child{ + display: flex; + justify-content: center; +} + +.update-sheet-form .form-wrapper .form form > div:last-child button{ + margin: 0; + width: 100%; + font-size: 18px; + font-weight: 600; + max-width: unset; + padding: 12px 10px; + border-radius: 10px; + font-family: "Nunito-Semibold"; + background-color: var(--orange); +} + +.update-sheet-form .form-wrapper .form form > div:last-child button:hover{ + opacity: 0.8; + transition: all 0.1s ease-in; +} + +.update-sheet-form .form-wrapper .form form > div:last-child button:focus{ + color: var( --black); + transition: all 0.1s ease; + background-color: var(--white); +} + +@media screen and (min-width: 900px) { + +.section.update-sheet-form .form-wrapper .form form > div input, +.section.update-sheet-form .form-wrapper .form form > div select{ + max-width: unset; +} + +} + +@media screen and (min-width: 600px) { + +.section.update-sheet-form .form-wrapper .form form > div input, +.section.update-sheet-form .form-wrapper .form form > div select{ + max-width: unset; +} + +} + +@media screen and (max-width: 1024px) { +.update-sheet-form{ + padding: 40px 0; +} + +.update-sheet-form .form-wrapper .form form{ + width: 100%; + padding: 10px 12px; +} + +.update-sheet-form .form-wrapper .form form > div:nth-child(1) h2{ + font-size: 1.5rem; +} + +.update-sheet-form .form-wrapper .form form > div{ + margin-top: 18px; +} + +.update-sheet-form .form-wrapper .form form > div label{ + font-size: 1rem; + margin-bottom: 0.75rem; +} + +.section.update-sheet-form .form-wrapper .form form > div:nth-child(2) input, +.section.update-sheet-form .form-wrapper .form form > div:nth-child(3) select{ + padding: 8px 10px; + font-size: 0.875rem; +} + +.update-sheet-form .form-wrapper .form form > div:last-child button{ + font-size: 1rem; + padding: 8px 10px; +} + +} \ No newline at end of file diff --git a/styles/partnership/partnership.css b/styles/partnership/partnership.css index e5287a1943..f4a00ebb11 100644 --- a/styles/partnership/partnership.css +++ b/styles/partnership/partnership.css @@ -66,6 +66,11 @@ object-fit: contain; } +.section.partnership-image-popup .wrappercreation-wrapper .offer-documents-wrapper:nth-child(5) .offer-documents.block .richtext.text>.cmp-text div picture>img, +.section.partnership-image-popup .wrappercreation-wrapper .offer-documents-wrapper:nth-child(14) .offer-documents.block .richtext.text>.cmp-text div picture>img { + object-fit: cover; +} + .section.partnership-image-popup .wrappercreation-wrapper .offer-documents-wrapper .offer-documents.block .richtext.text .stake-pop-up .popup .popupText .cmp-text .cpm-sub-text { display: block; /* margin: 0 2pc; */ @@ -165,6 +170,11 @@ line-height: 24px; text-decoration: none; } + +.section.partnership-image-popup .wrappercreation-wrapper .offer-documents-wrapper .offer-documents.block .richtext.text .stake-pop-up .text.popupText .cmp-text .popup-parent-cont .popupContainer div p a { + display: block; +} + .section.partnership-image-popup .wrappercreation-wrapper .offer-documents-wrapper .offer-documents.block .richtext.text .stake-pop-up .text.popupText .cmp-text .popup-parent-cont .popupContainer div h2{ color: rgba(0, 0, 0, .871); display: block;