From 0105217774f0fdb3c586ec31eb474763ccbc55be Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 11:25:33 +0100 Subject: [PATCH 01/14] handle the yc-select style --- themes/origins/assets/component-select.css | 118 +++++++++++++++++ themes/origins/styles/component-select.scss | 140 ++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 themes/origins/assets/component-select.css create mode 100644 themes/origins/styles/component-select.scss diff --git a/themes/origins/assets/component-select.css b/themes/origins/assets/component-select.css new file mode 100644 index 00000000..2bd66fb2 --- /dev/null +++ b/themes/origins/assets/component-select.css @@ -0,0 +1,118 @@ +yc-select{ + --select-bg-color:var(--section-bg-color, var(--color-base-white)); + --select-text-color:rgb(var(--section-text-color, var(--color-base-black))); + width:100%; + display:flex; + gap:var(--gap-md); + align-items:center; + position:relative; +} +yc-select yc-select-trigger{ + width:100%; + cursor:pointer; + font-size:var(--text-sm); + padding:8px 12px !important; + justify-content:space-between !important; +} +yc-select yc-select-trigger::after{ + content:""; + width:6px; + height:6px; + rotate:45deg; + position:relative; + border-radius:1px; + inset-block-start:-2px; + border-block-end:1.5px solid currentColor; + border-inline-end:1.5px solid currentColor; + transition:var(--transition-duration-normal); +} +yc-select yc-select-content{ + display:grid; + grid-gap:var(--gap-md); + gap:var(--gap-md); + z-index:2; + width:100%; + position:absolute; + inset-block-start:calc(100% + 0.5em); + border-radius:var(--radius-md); + background-color:var(--color-base-white); + transition:var(--transition-duration-normal); + border:1px solid color-mix(in srgb, currentColor 20%, transparent); +} +yc-select yc-select-content:not([data-visible=true]){ + opacity:0; + filter:blur(2px); + visibility:hidden; + translate:0 -0.5em; +} +yc-select yc-select-content:not([data-visible=true])[is-above]{ + translate:0 0.5em; +} +yc-select yc-select-content[is-above]{ + inset-block-start:auto; + inset-block-end:calc(100% + 0.5em); +} +yc-select yc-select-content input[type=text]{ + z-index:2; + position:sticky; + inset-block-start:0; + padding:10px 12px; + color:var(--select-text-color); + background-color:var(--select-bg-color); + border-block-end:1px solid color-mix(in srgb, currentColor 8%, transparent) !important; +} +yc-select yc-select-content label{ + padding:8px 12px; + position:relative; + margin-inline:4px; + font-size:var(--text-sm); + border-radius:var(--radius-md); + transition:var(--transition-duration-normal); +} +yc-select yc-select-content label[hidden-item]{ + display:none; +} +yc-select yc-select-content label:first-child{ + margin-block-start:4px; +} +yc-select yc-select-content label:last-child{ + margin-block-end:4px; +} +yc-select yc-select-content label::after{ + width:10px; + height:5px; + rotate:315deg; + position:absolute; + translate:-50% -50%; + inset-inline-end:4px; + inset-block-start:50%; + border:solid currentColor; + border-width:0 0 1.5px 1.5px; +} +[dir="rtl"] yc-select yc-select-content label::after{ + inset-inline-end:16px; +} +yc-select yc-select-content label:has(input:checked)::after{ + content:""; +} +yc-select yc-select-content label:has(input:disabled){ + opacity:0.5; + pointer-events:none; +} +yc-select yc-select-content label:hover{ + background-color:color-mix(in srgb, currentColor 5%, transparent); +} +yc-select yc-select-content:has(label:nth-of-type(n + 6)){ + max-height:260px; + overflow-y:scroll; +} +yc-select yc-select-content[data-no-results]::after{ + content:attr(data-no-results); + padding:3em 4px; + text-align:center; + font-size:var(--text-sm); +} +yc-select:has(yc-select-content[data-visible=true]) yc-select-trigger::after{ + rotate:-135deg; + inset-block-start:2px; +} diff --git a/themes/origins/styles/component-select.scss b/themes/origins/styles/component-select.scss new file mode 100644 index 00000000..69282492 --- /dev/null +++ b/themes/origins/styles/component-select.scss @@ -0,0 +1,140 @@ +yc-select { + --select-bg-color: var(--section-bg-color, var(--color-base-white)); + --select-text-color: rgb(var(--section-text-color, var(--color-base-black))); + + width: 100%; + display: flex; + gap: var(--gap-md); + align-items: center; + position: relative; + + yc-select-trigger { + width: 100%; + cursor: pointer; + font-size: var(--text-sm); + padding: 8px 12px !important; + justify-content: space-between !important; + + &::after { + content: ""; + width: 6px; + height: 6px; + rotate: 45deg; + position: relative; + border-radius: 1px; + inset-block-start: -2px; + border-block-end: 1.5px solid currentColor; + border-inline-end: 1.5px solid currentColor; + transition: var(--transition-duration-normal); + } + } + + yc-select-content { + display: grid; + gap: var(--gap-md); + z-index: 2; + width: 100%; + position: absolute; + inset-block-start: calc(100% + 0.5em); + border-radius: var(--radius-md); + background-color: var(--color-base-white); + transition: var(--transition-duration-normal); + border: 1px solid color-mix(in srgb, currentColor 20%, transparent); + + &:not([data-visible="true"]) { + opacity: 0; + filter: blur(2px); + visibility: hidden; + translate: 0 -0.5em; + + &[is-above] { + translate: 0 0.5em; + } + } + + &[is-above] { + inset-block-start: auto; + inset-block-end: calc(100% + 0.5em); + } + + input[type="text"] { + z-index: 2; + position: sticky; + inset-block-start: 0; + padding: 10px 12px; + color: var(--select-text-color); + background-color: var(--select-bg-color); + border-block-end: 1px solid + color-mix(in srgb, currentColor 8%, transparent) !important; + } + + label { + padding: 8px 12px; + position: relative; + margin-inline: 4px; + font-size: var(--text-sm); + border-radius: var(--radius-md); + transition: var(--transition-duration-normal); + + &[hidden-item] { + display: none; + } + + &:first-child { + margin-block-start: 4px; + } + + &:last-child { + margin-block-end: 4px; + } + + &::after { + width: 10px; + height: 5px; + rotate: 315deg; + position: absolute; + translate: -50% -50%; + inset-inline-end: 4px; + inset-block-start: 50%; + border: solid currentColor; + border-width: 0 0 1.5px 1.5px; + + &:dir(rtl) { + inset-inline-end: 16px; + } + } + + &:has(input:checked)::after { + content: ""; + } + + &:has(input:disabled) { + opacity: 0.5; + pointer-events: none; + } + + &:hover { + background-color: color-mix(in srgb, currentColor 5%, transparent); + } + } + + &:has(label:nth-of-type(n + 6)) { + max-height: 260px; + overflow-y: scroll; + } + + &[data-no-results] { + &::after { + content: attr(data-no-results); + padding: 3em 4px; + text-align: center; + font-size: var(--text-sm); + } + } + } + + &:has(yc-select-content[data-visible="true"]) yc-select-trigger::after { + rotate: -135deg; + inset-block-start: 2px; + } +} From 890c8e81a3baabacd3bbf7519e255f9f41102f6b Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 11:26:03 +0100 Subject: [PATCH 02/14] handle the yc-select logic (beta) --- themes/origins/assets/component-select.js | 111 ++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 themes/origins/assets/component-select.js diff --git a/themes/origins/assets/component-select.js b/themes/origins/assets/component-select.js new file mode 100644 index 00000000..32afee66 --- /dev/null +++ b/themes/origins/assets/component-select.js @@ -0,0 +1,111 @@ +class Select extends HTMLElement { + static observedAttributes = ["name"]; + + constructor() { + super(); + + this.state = false; + this.trigger = this.querySelector("yc-select-trigger"); + this.content = this.querySelector("yc-select-content"); + this.search = this.querySelector("yc-select-search"); + } + + connectedCallback() { + this._render(); + } + + _render() { + this.setup(); + this.control(); + this.onSelect(); + this.search && this.onSearch(); + } + + onSelect() { + const options = this.content.querySelectorAll("label"); + + options.forEach((opt) => + opt.addEventListener("change", () => { + this.trigger.textContent = opt.textContent; + }), + ); + } + + onSearch() { + const placeholder = this.search.getAttribute("placeholder"); + const noResults = this.search.getAttribute("no-results"); + + const template = document.createElement("template"); + template.innerHTML = ``; + + const searchInput = template.content.firstElementChild.cloneNode(true); + this.search.replaceWith(searchInput); + + const options = this.content.querySelectorAll("label"); + + searchInput.addEventListener("input", (e) => { + const isValid = (option) => + option.textContent.toLowerCase().includes(e.target.value.toLowerCase()); + + Array.from(options).forEach((opt) => { + opt.toggleAttribute("hidden-item", !isValid(opt)); + }); + + Array.from(options).filter((opt) => isValid(opt)).length + ? this.content.removeAttribute("data-no-results") + : this.content.setAttribute("data-no-results", noResults); + }); + } + + control() { + const setState = (visible) => { + this.state = visible; + this.content.dataset.visible = visible; + document.body.toggleAttribute("data-scroll-locked", visible); + }; + + const clickOutside = (e) => + !e.composedPath().includes(this.trigger) && setState(false); + + const toggle = () => { + this.content.toggleAttribute( + "is-above", + innerHeight - this.trigger.getBoundingClientRect().bottom < + this.content.offsetHeight, + ); + + setState(!this.state); + }; + + this.trigger.addEventListener("click", toggle); + document.addEventListener("click", clickOutside); + } + + setup() { + const items = this.content.querySelectorAll("yc-select-item"); + + items.forEach((item) => { + const label = item.textContent; + const value = item.getAttribute("value"); + const disabled = item.hasAttribute("disabled"); + + const template = document.createElement("template"); + template.innerHTML = ` + + `; + + const option = template.content.firstElementChild.cloneNode(true); + item.replaceWith(option); + }); + } +} + +customElements.define("yc-select", Select); From 3f2fc66898ee4163af22c2d39ea4174e589557b3 Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 14:56:47 +0100 Subject: [PATCH 03/14] logic > new update --- themes/origins/assets/component-select.js | 132 +++++++++++----------- 1 file changed, 64 insertions(+), 68 deletions(-) diff --git a/themes/origins/assets/component-select.js b/themes/origins/assets/component-select.js index 32afee66..38008327 100644 --- a/themes/origins/assets/component-select.js +++ b/themes/origins/assets/component-select.js @@ -3,7 +3,6 @@ class Select extends HTMLElement { constructor() { super(); - this.state = false; this.trigger = this.querySelector("yc-select-trigger"); this.content = this.querySelector("yc-select-content"); @@ -11,99 +10,96 @@ class Select extends HTMLElement { } connectedCallback() { - this._render(); - } - - _render() { this.setup(); - this.control(); - this.onSelect(); - this.search && this.onSearch(); + this.listeners(); + this.search && this.enableSearch(); } - onSelect() { - const options = this.content.querySelectorAll("label"); + setup() { + const items = this.content.querySelectorAll("yc-select-item"); - options.forEach((opt) => - opt.addEventListener("change", () => { - this.trigger.textContent = opt.textContent; - }), - ); + items.forEach((item) => { + const { value } = item.attributes; + const label = item.textContent.trim(); + const disabled = item.hasAttribute("disabled"); + item.outerHTML = ` + `; + }); } - onSearch() { - const placeholder = this.search.getAttribute("placeholder"); - const noResults = this.search.getAttribute("no-results"); + listeners() { + this.onSelect(); + this.onTrigger(); + this.onClickOutSide(); + } - const template = document.createElement("template"); - template.innerHTML = ``; + enableSearch() { + const placeholder = this.search.getAttribute("placeholder"); + const noResultsMsg = this.search.getAttribute("no-results"); + const searchInput = document.createElement("input"); + searchInput.type = "text"; + searchInput.name = "search"; + searchInput.placeholder = placeholder; - const searchInput = template.content.firstElementChild.cloneNode(true); this.search.replaceWith(searchInput); + this.search = searchInput; - const options = this.content.querySelectorAll("label"); - - searchInput.addEventListener("input", (e) => { - const isValid = (option) => - option.textContent.toLowerCase().includes(e.target.value.toLowerCase()); - - Array.from(options).forEach((opt) => { - opt.toggleAttribute("hidden-item", !isValid(opt)); - }); + const options = [...this.content.querySelectorAll("label")]; + this.onSearch(options, noResultsMsg); + } - Array.from(options).filter((opt) => isValid(opt)).length - ? this.content.removeAttribute("data-no-results") - : this.content.setAttribute("data-no-results", noResults); - }); + toggleState(visible) { + this.state = visible; + this.content.dataset.visible = visible; } - control() { - const setState = (visible) => { - this.state = visible; - this.content.dataset.visible = visible; - document.body.toggleAttribute("data-scroll-locked", visible); - }; + onSelect() { + const options = this.content.querySelectorAll("label"); - const clickOutside = (e) => - !e.composedPath().includes(this.trigger) && setState(false); + options.forEach((opt) => + opt.addEventListener("change", () => { + this.trigger.textContent = opt.textContent; + this.toggleState(false); + }), + ); + } - const toggle = () => { + onTrigger() { + this.trigger.addEventListener("click", () => { this.content.toggleAttribute( "is-above", innerHeight - this.trigger.getBoundingClientRect().bottom < this.content.offsetHeight, ); + this.toggleState(!this.state); + }); + } - setState(!this.state); - }; + onClickOutSide() { + document.addEventListener("click", (e) => { + const isTrigger = e.composedPath().includes(this.trigger); + const isSearch = e.target.tagName === "INPUT"; - this.trigger.addEventListener("click", toggle); - document.addEventListener("click", clickOutside); + if (!isTrigger && !isSearch) this.toggleState(false); + }); } - setup() { - const items = this.content.querySelectorAll("yc-select-item"); + onSearch(options, no_results_message) { + this.search.addEventListener("input", (e) => { + const query = e.target.value.toLowerCase(); + let hasMatch = false; - items.forEach((item) => { - const label = item.textContent; - const value = item.getAttribute("value"); - const disabled = item.hasAttribute("disabled"); + options.forEach((opt) => { + const isMatch = opt.textContent.toLowerCase().includes(query); + opt.hidden = !isMatch; + hasMatch ||= isMatch; + }); - const template = document.createElement("template"); - template.innerHTML = ` - - `; - - const option = template.content.firstElementChild.cloneNode(true); - item.replaceWith(option); + this.content.dataset.noResults = hasMatch ? "" : no_results_message; }); } } From b574641c6f822c2aca713d57828c16bf5daad930 Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 14:57:14 +0100 Subject: [PATCH 04/14] tiny UX improvements --- themes/origins/assets/component-select.css | 11 +++++++---- themes/origins/styles/component-select.scss | 11 ++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/themes/origins/assets/component-select.css b/themes/origins/assets/component-select.css index 2bd66fb2..64d85b2d 100644 --- a/themes/origins/assets/component-select.css +++ b/themes/origins/assets/component-select.css @@ -10,6 +10,9 @@ yc-select{ yc-select yc-select-trigger{ width:100%; cursor:pointer; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; font-size:var(--text-sm); padding:8px 12px !important; justify-content:space-between !important; @@ -69,9 +72,6 @@ yc-select yc-select-content label{ border-radius:var(--radius-md); transition:var(--transition-duration-normal); } -yc-select yc-select-content label[hidden-item]{ - display:none; -} yc-select yc-select-content label:first-child{ margin-block-start:4px; } @@ -102,11 +102,14 @@ yc-select yc-select-content label:has(input:disabled){ yc-select yc-select-content label:hover{ background-color:color-mix(in srgb, currentColor 5%, transparent); } +yc-select yc-select-content:has(label[hidden]){ + padding-block-end:4px; +} yc-select yc-select-content:has(label:nth-of-type(n + 6)){ max-height:260px; overflow-y:scroll; } -yc-select yc-select-content[data-no-results]::after{ +yc-select yc-select-content[data-no-results]:not([data-no-results=""])::after{ content:attr(data-no-results); padding:3em 4px; text-align:center; diff --git a/themes/origins/styles/component-select.scss b/themes/origins/styles/component-select.scss index 69282492..750fe85a 100644 --- a/themes/origins/styles/component-select.scss +++ b/themes/origins/styles/component-select.scss @@ -11,6 +11,7 @@ yc-select { yc-select-trigger { width: 100%; cursor: pointer; + user-select: none; font-size: var(--text-sm); padding: 8px 12px !important; justify-content: space-between !important; @@ -76,10 +77,6 @@ yc-select { border-radius: var(--radius-md); transition: var(--transition-duration-normal); - &[hidden-item] { - display: none; - } - &:first-child { margin-block-start: 4px; } @@ -118,12 +115,16 @@ yc-select { } } + &:has(label[hidden]) { + padding-block-end: 4px; + } + &:has(label:nth-of-type(n + 6)) { max-height: 260px; overflow-y: scroll; } - &[data-no-results] { + &[data-no-results]:not([data-no-results=""]) { &::after { content: attr(data-no-results); padding: 3em 4px; From a1b11ff232818d68842ad3a53e912e221095f2bd Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 14:57:31 +0100 Subject: [PATCH 05/14] make the scroll-locked global --- themes/origins/assets/main.css | 2 +- themes/origins/styles/base/_global.scss | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/themes/origins/assets/main.css b/themes/origins/assets/main.css index e467297b..4450b6ae 100644 --- a/themes/origins/assets/main.css +++ b/themes/origins/assets/main.css @@ -16,7 +16,7 @@ body{ color:rgb(var(--color-primary)); background-color:var(--color-secondary); } -body[data-scroll-locked]{ +body:has(* [data-visible=true]), body:has(* [data-hidden=false]){ overflow:hidden; } body .main-container{ diff --git a/themes/origins/styles/base/_global.scss b/themes/origins/styles/base/_global.scss index 57ccc242..9dbf35ce 100644 --- a/themes/origins/styles/base/_global.scss +++ b/themes/origins/styles/base/_global.scss @@ -16,7 +16,8 @@ body { color: rgb(var(--color-primary)); background-color: var(--color-secondary); - &[data-scroll-locked] { + &:has(* [data-visible="true"]), + &:has(* [data-hidden="false"]) { overflow: hidden; } From 3cc9120f8e9ede55e66a943d6e6e61af244caffe Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 15:46:39 +0100 Subject: [PATCH 06/14] handle the long values style --- themes/origins/assets/component-select.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/themes/origins/assets/component-select.css b/themes/origins/assets/component-select.css index 64d85b2d..7b295426 100644 --- a/themes/origins/assets/component-select.css +++ b/themes/origins/assets/component-select.css @@ -17,6 +17,12 @@ yc-select yc-select-trigger{ padding:8px 12px !important; justify-content:space-between !important; } +yc-select yc-select-trigger yc-select-value{ + width:90%; + overflow:hidden; + white-space:nowrap; + text-overflow:ellipsis; +} yc-select yc-select-trigger::after{ content:""; width:6px; @@ -72,6 +78,13 @@ yc-select yc-select-content label{ border-radius:var(--radius-md); transition:var(--transition-duration-normal); } +yc-select yc-select-content label span{ + overflow:hidden; + display:-webkit-box; + -webkit-line-clamp:1; + text-overflow:ellipsis; + -webkit-box-orient:vertical; +} yc-select yc-select-content label:first-child{ margin-block-start:4px; } From e4479143f12c9304f7aadf7641876bd1796c1080 Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 15:46:55 +0100 Subject: [PATCH 07/14] logic > tiny addition --- themes/origins/assets/component-select.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/themes/origins/assets/component-select.js b/themes/origins/assets/component-select.js index 38008327..a83eecf1 100644 --- a/themes/origins/assets/component-select.js +++ b/themes/origins/assets/component-select.js @@ -5,6 +5,7 @@ class Select extends HTMLElement { super(); this.state = false; this.trigger = this.querySelector("yc-select-trigger"); + this.placeholder = this.trigger.querySelector("yc-select-value"); this.content = this.querySelector("yc-select-content"); this.search = this.querySelector("yc-select-search"); } @@ -24,7 +25,7 @@ class Select extends HTMLElement { const disabled = item.hasAttribute("disabled"); item.outerHTML = ` `; @@ -62,7 +63,7 @@ class Select extends HTMLElement { options.forEach((opt) => opt.addEventListener("change", () => { - this.trigger.textContent = opt.textContent; + this.placeholder.textContent = opt.textContent.trim(); this.toggleState(false); }), ); From 352b58d31ef73bfb0ccbfd31c8ddfa2c7ae65186 Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 15:47:31 +0100 Subject: [PATCH 08/14] dropdown variant > use the yc-select --- themes/origins/assets/component-variants.css | 84 ---------------- .../origins/snippets/variant-dropdown.liquid | 27 +++-- themes/origins/styles/component-select.scss | 15 +++ themes/origins/styles/component-variants.scss | 98 ------------------- 4 files changed, 28 insertions(+), 196 deletions(-) diff --git a/themes/origins/assets/component-variants.css b/themes/origins/assets/component-variants.css index f66a6cee..2033a76a 100644 --- a/themes/origins/assets/component-variants.css +++ b/themes/origins/assets/component-variants.css @@ -160,90 +160,6 @@ yc-variant[name=image] .image-item:has(input:disabled)::before{ yc-variant[name=image] .image-item:hover img{ opacity:0.8; } -yc-variant[name=dropdown]{ - position:relative; -} -yc-variant[name=dropdown] .dropdown-trigger{ - width:100%; - padding:8px 12px; - font-size:var(--text-sm); - justify-content:space-between; - color:var(--variant-text-color); -} -yc-variant[name=dropdown] .dropdown-trigger span[data-placeholder]{ - overflow:hidden; - width:-moz-fit-content; - width:fit-content; - white-space:nowrap; - text-overflow:ellipsis; -} -yc-variant[name=dropdown] .dropdown-trigger svg{ - transition:var(--transition-duration-normal); -} -yc-variant[name=dropdown] .dropdown-trigger:has(~ .dropdown-options[data-hidden=false]) svg{ - rotate:180deg; -} -yc-variant[name=dropdown] .dropdown-options{ - display:grid; - grid-gap:var(--gap-md); - gap:var(--gap-md); - width:100%; - padding:4px; - z-index:2; - position:absolute; - inset-block-start:calc(100% + 0.5em); - border-radius:var(--radius-md); - background-color:var(--variant-bg-color); - transition:var(--transition-duration-normal); - border:1px solid color-mix(in srgb, currentColor 20%, transparent); -} -yc-variant[name=dropdown] .dropdown-options[data-hidden=true]{ - opacity:0; - filter:blur(2px); - visibility:hidden; - translate:0 -0.5em; -} -yc-variant[name=dropdown] .dropdown-options[data-hidden=true][is-above]{ - translate:0 0.5em; -} -yc-variant[name=dropdown] .dropdown-options[is-above]{ - inset-block-start:auto; - inset-block-end:calc(100% + 0.5em); -} -yc-variant[name=dropdown] .dropdown-options .option{ - padding:8px 12px; - position:relative; - font-size:var(--text-sm); - border-radius:var(--radius-md); - transition:var(--transition-duration-normal); -} -yc-variant[name=dropdown] .dropdown-options .option::before{ - content:var(--label); -} -yc-variant[name=dropdown] .dropdown-options .option::after{ - width:10px; - height:5px; - rotate:315deg; - position:absolute; - translate:-50% -50%; - inset-inline-end:4px; - inset-block-start:50%; - border:solid currentColor; - border-width:0 0 1.5px 1.5px; -} -[dir="rtl"] yc-variant[name=dropdown] .dropdown-options .option::after{ - inset-inline-end:16px; -} -yc-variant[name=dropdown] .dropdown-options .option:has(input:checked)::after{ - content:""; -} -yc-variant[name=dropdown] .dropdown-options .option:has(input:disabled){ - opacity:0.5; - pointer-events:none; -} -yc-variant[name=dropdown] .dropdown-options .option:hover{ - background-color:color-mix(in srgb, currentColor 5%, transparent); -} yc-variant[name=upload-image]{ display:grid; grid-gap:var(--gap-md); diff --git a/themes/origins/snippets/variant-dropdown.liquid b/themes/origins/snippets/variant-dropdown.liquid index 982d39db..d2ee48a6 100644 --- a/themes/origins/snippets/variant-dropdown.liquid +++ b/themes/origins/snippets/variant-dropdown.liquid @@ -2,6 +2,7 @@ Renders a dropdown select (Product variant) Accepts: + - placeholder: {String} input placeholder - options: {Array} variant options - name: {String} input name @@ -10,18 +11,16 @@ {% endcomment %} - - + + + {{ placeholder | default: 'Select' }} + + + {% for option in options %} + + {{ option }} + + {% endfor %} + + diff --git a/themes/origins/styles/component-select.scss b/themes/origins/styles/component-select.scss index 750fe85a..01673791 100644 --- a/themes/origins/styles/component-select.scss +++ b/themes/origins/styles/component-select.scss @@ -16,6 +16,13 @@ yc-select { padding: 8px 12px !important; justify-content: space-between !important; + yc-select-value { + width: 90%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + &::after { content: ""; width: 6px; @@ -77,6 +84,14 @@ yc-select { border-radius: var(--radius-md); transition: var(--transition-duration-normal); + span { + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 1; + text-overflow: ellipsis; + -webkit-box-orient: vertical; + } + &:first-child { margin-block-start: 4px; } diff --git a/themes/origins/styles/component-variants.scss b/themes/origins/styles/component-variants.scss index 15bdf0d9..d1777609 100644 --- a/themes/origins/styles/component-variants.scss +++ b/themes/origins/styles/component-variants.scss @@ -203,104 +203,6 @@ yc-variant { } } - &[name="dropdown"] { - position: relative; - - .dropdown-trigger { - width: 100%; - padding: 8px 12px; - font-size: var(--text-sm); - justify-content: space-between; - color: var(--variant-text-color); - - span[data-placeholder] { - overflow: hidden; - width: fit-content; - white-space: nowrap; - text-overflow: ellipsis; - } - - svg { - transition: var(--transition-duration-normal); - } - - &:has(~ .dropdown-options[data-hidden="false"]) svg { - rotate: 180deg; - } - } - - .dropdown-options { - display: grid; - gap: var(--gap-md); - width: 100%; - padding: 4px; - z-index: 2; - position: absolute; - inset-block-start: calc(100% + 0.5em); - border-radius: var(--radius-md); - background-color: var(--variant-bg-color); - transition: var(--transition-duration-normal); - border: 1px solid color-mix(in srgb, currentColor 20%, transparent); - - &[data-hidden="true"] { - opacity: 0; - filter: blur(2px); - visibility: hidden; - translate: 0 -0.5em; - - &[is-above] { - translate: 0 0.5em; - } - } - - &[is-above] { - inset-block-start: auto; - inset-block-end: calc(100% + 0.5em); - } - - .option { - padding: 8px 12px; - position: relative; - font-size: var(--text-sm); - border-radius: var(--radius-md); - transition: var(--transition-duration-normal); - - &::before { - content: var(--label); - } - - &::after { - width: 10px; - height: 5px; - rotate: 315deg; - position: absolute; - translate: -50% -50%; - inset-inline-end: 4px; - inset-block-start: 50%; - border: solid currentColor; - border-width: 0 0 1.5px 1.5px; - - &:dir(rtl) { - inset-inline-end: 16px; - } - } - - &:has(input:checked)::after { - content: ""; - } - - &:has(input:disabled) { - opacity: 0.5; - pointer-events: none; - } - - &:hover { - background-color: color-mix(in srgb, currentColor 5%, transparent); - } - } - } - } - &[name="upload-image"] { display: grid; gap: var(--gap-md); From ed9c30ce4f23420bda596bc65570d1b96291504e Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Wed, 22 Jan 2025 15:47:48 +0100 Subject: [PATCH 09/14] playground --- themes/origins/sections/default.liquid | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/themes/origins/sections/default.liquid b/themes/origins/sections/default.liquid index 4e1dff81..2c45ab87 100644 --- a/themes/origins/sections/default.liquid +++ b/themes/origins/sections/default.liquid @@ -3,10 +3,38 @@ {{ 'component-variants.css' | asset_url | stylesheet_tag }} {{ 'component-variants.js' | asset_url | script_tag_deferred }} +{{ 'component-select.css' | asset_url | stylesheet_tag }} +{{ 'component-select.js' | asset_url | script_tag_deferred }} + {% comment %} TO REMOVE ❌ TODO: This section is just a placeholder for the theme pages, as the template schema requires at least one section {% endcomment %} + +
+ + + Please select a value + + + + United States + Canada + Mexico + Brazil + Argentina + United Kingdom + Germany + France + Italy + Japan + Australia + India + + +
+
+
- + Please select a value From cecb4509a4e65812b18c9f206909c6188c4aba95 Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Thu, 23 Jan 2025 10:04:12 +0100 Subject: [PATCH 12/14] improvements --- themes/origins/assets/component-select.css | 15 +- themes/origins/assets/component-select.js | 202 ++++++++-------- themes/origins/assets/component-variants.js | 39 --- themes/origins/assets/featured-product.css | 252 ++++++++++++++++++++ themes/origins/styles/component-select.scss | 10 +- 5 files changed, 366 insertions(+), 152 deletions(-) create mode 100644 themes/origins/assets/featured-product.css diff --git a/themes/origins/assets/component-select.css b/themes/origins/assets/component-select.css index 43ea2ce2..c91282db 100644 --- a/themes/origins/assets/component-select.css +++ b/themes/origins/assets/component-select.css @@ -7,23 +7,23 @@ yc-select{ align-items:center; position:relative; } -yc-select yc-select-trigger{ +yc-select yc-select-trigger.yc-btn.icon{ width:100%; cursor:pointer; -webkit-user-select:none; -moz-user-select:none; user-select:none; font-size:var(--text-sm); - padding:8px 12px !important; - justify-content:space-between !important; + padding:8px 12px; + justify-content:space-between; } -yc-select yc-select-trigger yc-select-value{ +yc-select yc-select-trigger.yc-btn.icon yc-select-value{ width:90%; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; } -yc-select yc-select-trigger::after{ +yc-select yc-select-trigger.yc-btn.icon::after{ content:""; width:6px; height:6px; @@ -37,8 +37,6 @@ yc-select yc-select-trigger::after{ } yc-select yc-select-content{ display:grid; - grid-gap:var(--gap-md); - gap:var(--gap-md); z-index:2; width:100%; position:absolute; @@ -61,10 +59,11 @@ yc-select yc-select-content[is-above]{ inset-block-start:auto; inset-block-end:calc(100% + 0.5em); } -yc-select yc-select-content input[type=text]{ +yc-select yc-select-content input[type=search]{ z-index:2; position:sticky; inset-block-start:0; + margin-block-end:4px; padding:10px 12px; color:var(--select-text-color); background-color:var(--select-bg-color); diff --git a/themes/origins/assets/component-select.js b/themes/origins/assets/component-select.js index a83eecf1..cddd18b0 100644 --- a/themes/origins/assets/component-select.js +++ b/themes/origins/assets/component-select.js @@ -1,108 +1,110 @@ -class Select extends HTMLElement { - static observedAttributes = ["name"]; - - constructor() { - super(); - this.state = false; - this.trigger = this.querySelector("yc-select-trigger"); - this.placeholder = this.trigger.querySelector("yc-select-value"); - this.content = this.querySelector("yc-select-content"); - this.search = this.querySelector("yc-select-search"); - } - - connectedCallback() { - this.setup(); - this.listeners(); - this.search && this.enableSearch(); - } - - setup() { - const items = this.content.querySelectorAll("yc-select-item"); - - items.forEach((item) => { - const { value } = item.attributes; - const label = item.textContent.trim(); - const disabled = item.hasAttribute("disabled"); - item.outerHTML = ` - `; - }); - } - - listeners() { - this.onSelect(); - this.onTrigger(); - this.onClickOutSide(); - } - - enableSearch() { - const placeholder = this.search.getAttribute("placeholder"); - const noResultsMsg = this.search.getAttribute("no-results"); - const searchInput = document.createElement("input"); - searchInput.type = "text"; - searchInput.name = "search"; - searchInput.placeholder = placeholder; - - this.search.replaceWith(searchInput); - this.search = searchInput; - - const options = [...this.content.querySelectorAll("label")]; - this.onSearch(options, noResultsMsg); - } - - toggleState(visible) { - this.state = visible; - this.content.dataset.visible = visible; - } - - onSelect() { - const options = this.content.querySelectorAll("label"); - - options.forEach((opt) => - opt.addEventListener("change", () => { - this.placeholder.textContent = opt.textContent.trim(); - this.toggleState(false); - }), - ); - } - - onTrigger() { - this.trigger.addEventListener("click", () => { - this.content.toggleAttribute( - "is-above", - innerHeight - this.trigger.getBoundingClientRect().bottom < - this.content.offsetHeight, +if (!customElements.get("yc-select")) { + class Select extends HTMLElement { + static observedAttributes = ["name"]; + + constructor() { + super(); + this.state = false; + this.trigger = this.querySelector("yc-select-trigger"); + this.placeholder = this.trigger.querySelector("yc-select-value"); + this.content = this.querySelector("yc-select-content"); + this.search = this.querySelector("yc-select-search"); + } + + connectedCallback() { + this.setup(); + this.listeners(); + this.search && this.enableSearch(); + } + + setup() { + const items = this.content.querySelectorAll("yc-select-item"); + + items.forEach((item) => { + const { value } = item.attributes; + const label = item.textContent.trim(); + const disabled = item.hasAttribute("disabled"); + item.outerHTML = ` + `; + }); + } + + listeners() { + this.onSelect(); + this.onTrigger(); + this.onClickOutSide(); + } + + enableSearch() { + const placeholder = this.search.getAttribute("placeholder"); + const noResultsMsg = this.search.getAttribute("no-results"); + const searchInput = document.createElement("input"); + searchInput.type = "search"; + searchInput.name = "search"; + searchInput.placeholder = placeholder; + + this.search.replaceWith(searchInput); + this.search = searchInput; + + const options = [...this.content.querySelectorAll("label")]; + this.onSearch(options, noResultsMsg); + } + + toggleState(visible) { + this.state = visible; + this.content.dataset.visible = visible; + } + + onSelect() { + const options = this.content.querySelectorAll("label"); + + options.forEach((opt) => + opt.addEventListener("change", () => { + this.placeholder.textContent = opt.textContent.trim(); + this.toggleState(false); + }), ); - this.toggleState(!this.state); - }); - } + } + + onTrigger() { + this.trigger.addEventListener("click", () => { + this.content.toggleAttribute( + "is-above", + innerHeight - this.trigger.getBoundingClientRect().bottom < + this.content.offsetHeight, + ); + this.toggleState(!this.state); + }); + } - onClickOutSide() { - document.addEventListener("click", (e) => { - const isTrigger = e.composedPath().includes(this.trigger); - const isSearch = e.target.tagName === "INPUT"; + onClickOutSide() { + document.addEventListener("click", (e) => { + const isTrigger = e.composedPath().includes(this.trigger); + const isSearch = e.target.tagName === "INPUT"; - if (!isTrigger && !isSearch) this.toggleState(false); - }); - } + if (!isTrigger && !isSearch) this.toggleState(false); + }); + } - onSearch(options, no_results_message) { - this.search.addEventListener("input", (e) => { - const query = e.target.value.toLowerCase(); - let hasMatch = false; + onSearch(options, no_results_message) { + this.search.addEventListener("input", (e) => { + const query = e.target.value.toLowerCase(); + let hasMatch = false; - options.forEach((opt) => { - const isMatch = opt.textContent.toLowerCase().includes(query); - opt.hidden = !isMatch; - hasMatch ||= isMatch; - }); + options.forEach((opt) => { + const isMatch = opt.textContent.toLowerCase().includes(query); + opt.hidden = !isMatch; + hasMatch ||= isMatch; + }); - this.content.dataset.noResults = hasMatch ? "" : no_results_message; - }); + this.content.dataset.noResults = hasMatch ? "" : no_results_message; + }); + } } -} -customElements.define("yc-select", Select); + customElements.define("yc-select", Select); +} diff --git a/themes/origins/assets/component-variants.js b/themes/origins/assets/component-variants.js index fffdc233..f54309c3 100644 --- a/themes/origins/assets/component-variants.js +++ b/themes/origins/assets/component-variants.js @@ -11,48 +11,9 @@ class Variant extends HTMLElement { } _render() { - this.VARIANT_NAME === "dropdown" && this.dropdown(); this.VARIANT_NAME === "upload-image" && this.uploadImage(); } - dropdown() { - const trigger = this.querySelector("[data-trigger]"); - const content = this.querySelector("[data-content]"); - const options = content.querySelectorAll("[data-option]"); - const placeholder = trigger.querySelector("[data-placeholder]"); - let state = content.dataset.hidden === "true"; - - const setPlaceholder = (option) => { - placeholder.textContent = option.querySelector("span").textContent; - }; - - const setState = (hidden) => { - state = hidden; - content.dataset.hidden = hidden; - document.body.toggleAttribute("data-scroll-locked", !hidden); - }; - - const clickOutside = (e) => - !e.composedPath().includes(trigger) && setState(true); - - const toggle = () => { - content.toggleAttribute( - "is-above", - innerHeight - trigger.getBoundingClientRect().bottom < - content.offsetHeight, - ); - - setState(!state); - }; - - trigger.addEventListener("click", toggle); - document.addEventListener("click", clickOutside); - - options.forEach((opt) => - opt.addEventListener("change", () => setPlaceholder(opt)), - ); - } - uploadImage() { const input = this.querySelector("input[type='file']"); const info = this.querySelector("[data-info]"); diff --git a/themes/origins/assets/featured-product.css b/themes/origins/assets/featured-product.css new file mode 100644 index 00000000..f51c7a88 --- /dev/null +++ b/themes/origins/assets/featured-product.css @@ -0,0 +1,252 @@ +.single-product{ + --image-border:1px solid color-mix(in srgb, currentColor 5%, transparent); + position:relative; + display:grid; + grid-template-columns:repeat(2, 1fr); + grid-gap:var(--gap-3xl); + gap:var(--gap-3xl); +} +.single-product yc-product-media{ + --main-image-height:550px; + height:-moz-fit-content; + height:fit-content; + display:grid; + grid-gap:var(--gap-sm); + gap:var(--gap-sm); + position:sticky; + inset-block-start:var(--gap-lg); +} +.single-product yc-product-media img{ + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} +.single-product yc-product-media .main-image{ + position:relative; + width:100%; + height:var(--main-image-height); + border-radius:var(--media-main-image-border-radius); + border:var(--image-border); + overflow:hidden; +} +.single-product yc-product-media .main-image .count{ + position:absolute; + font-size:var(--text-md); + padding:0.5em 1.5em; + inset-inline-end:1em; + inset-block-end:1em; + border-radius:var(--radius-xl); + color:var(--section-bg-color, var(--color-base-white)); + -webkit-backdrop-filter:var(--overlay-blur); + backdrop-filter:var(--overlay-blur); + background-color:var(--overlay-background); +} +.single-product yc-product-media .main-image img{ + -o-object-fit:cover; + object-fit:cover; +} +.single-product yc-product-media .main-image .placeholder{ + height:100%; + width:100%; + display:flex; + justify-content:center; + align-items:center; + background-color:color-mix(in srgb, currentColor 5%, transparent); + color:color-mix(in srgb, currentColor 20%, transparent); +} +.single-product yc-product-media .gallery .slider-inner{ + padding:0.25em; +} +.single-product yc-product-media .gallery .slider-inner yc-slider-item{ + max-width:100px; +} +.single-product yc-product-media .gallery .slider-inner yc-slider-item .image{ + height:100px; + scroll-snap-align:start; + cursor:pointer; + display:inline-block; + width:100%; + border:var(--image-border); + border-radius:var(--media-gallery-border-radius); + overflow:hidden; + position:relative; + transition:var(--transition-duration-normal); +} +.single-product yc-product-media .gallery .slider-inner yc-slider-item .image::before{ + content:""; + position:absolute; + width:100%; + height:100%; + opacity:0; + border-radius:calc(var(--media-gallery-border-radius) - 1px); + -webkit-backdrop-filter:var(--media-blur); + backdrop-filter:var(--media-blur); + background-color:var(--media-background); + transition:var(--transition-duration-normal); +} +.single-product yc-product-media .gallery .slider-inner yc-slider-item .image:hover::before{ + opacity:1; +} +.single-product yc-product-media .gallery .slider-inner yc-slider-item .image:has(input:checked){ + box-shadow:var(--shadow-dark-focus-sm); +} +.single-product yc-product-media .gallery .slider-inner yc-slider-item .image:has(input:checked)::before{ + opacity:1; +} +.single-product yc-product-media .gallery .slider-inner yc-slider-item .image img{ + -o-object-fit:cover; + object-fit:cover; +} +.single-product yc-product-info{ + display:flex; + flex-direction:column; + gap:var(--gap-md); +} +.single-product yc-product-info .price{ + display:flex; + align-items:center; + gap:var(--gap-md); +} +.single-product yc-product-info .price .current{ + font-weight:550; +} +.single-product yc-product-info .price .compare-at{ + color:color-mix(in srgb, currentColor 70%, transparent); + -webkit-text-decoration:line-through; + text-decoration:line-through; +} +.single-product yc-product-info .description .card-placeholder{ + font-size:var(--text-md); +} +.single-product yc-product-info .inventory{ + display:flex; + align-items:center; + gap:var(--gap-2xs); +} +.single-product yc-product-info .inventory[data-inventory-out-of-stock] .icon{ + color:var(--color-gray-300); +} +.single-product yc-product-info .inventory[data-inventory-in-stock] .icon{ + color:var(--color-success-500); +} +.single-product yc-product-info .inventory[data-inventory-low-stock] .icon{ + color:var(--color-warning-500); +} +.single-product yc-product-info .accordion{ + border:1px solid var(--color-gray-50); + border-radius:var(--accordion-border-radius, var(--radius-md)); + height:-moz-fit-content; + height:fit-content; + color:rgb(var(--accordion-foreground)); +} +.single-product yc-product-info .accordion summary{ + display:flex; + justify-content:space-between; + align-items:center; + padding:12px 15px; + cursor:pointer; +} +.single-product yc-product-info .accordion summary .title{ + font-size:var(--text-md); + font-weight:500; + flex:1; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} +.single-product yc-product-info .accordion summary .icon{ + transition:var(--transition-duration-normal); +} +.single-product yc-product-info .accordion .content{ + font-size:var(--text-md); + padding:12px 15px; + color:color-mix(in srgb, currentColor 70%, transparent); +} +.single-product yc-product-info .accordion[open] summary .icon{ + rotate:180deg; +} +.single-product yc-product-info .buy-button{ + display:grid; + grid-gap:var(--gap-lg); + gap:var(--gap-lg); + margin-block-start:1em; +} +.single-product yc-product-info .buy-button .events{ + display:flex; + gap:var(--gap-md); +} +.single-product yc-product-info .buy-button .events button[data-trigger]{ + width:100%; +} +.single-product yc-product-info .buy-button .express-checkout{ + display:grid; + border-radius:var(--radius-md); + border:1px solid color-mix(in srgb, currentColor 10%, transparent); +} +.single-product yc-product-info .buy-button .express-checkout .header{ + display:grid; + grid-gap:var(--gap-sm); + gap:var(--gap-sm); + font-size:var(--text-md); + padding:1.5em; + border-block-end:1px solid color-mix(in srgb, currentColor 10%, transparent); +} +.single-product yc-product-info .buy-button .express-checkout .header .subtitle{ + font-size:var(--text-sm); + color:color-mix(in srgb, currentColor 80%, transparent); +} +.single-product yc-product-info .buy-button .express-checkout .fields{ + padding:1.5em; + display:grid; + grid-gap:var(--gap-lg); + gap:var(--gap-lg); + align-items:baseline; + grid-template-columns:repeat(2, 1fr); +} +.single-product yc-product-info .buy-button .express-checkout .fields .field{ + display:grid; + grid-gap:var(--gap-sm); + gap:var(--gap-sm); +} +.single-product yc-product-info .buy-button .express-checkout .fields .field label{ + font-size:var(--text-sm); + color:color-mix(in srgb, currentColor 80%, transparent); +} +.single-product yc-product-info .buy-button .express-checkout .fields .field input{ + color:currentColor; +} +.single-product yc-product-info .buy-button .express-checkout .fields .field:has(input:required) label::after{ + content:" *"; + color:var(--color-error-500); +} +.single-product yc-product-info .buy-button .express-checkout .fields .field[error-message]::after{ + content:attr(error-message); + font-size:var(--text-sm); + color:var(--color-error-500); +} +.single-product yc-product-info .buy-button .express-checkout .fields .field[error-message] input{ + border-color:var(--color-error-500); +} + +@media (max-width: 1280px){ + .single-product yc-product-media{ + --main-image-height:450px; + flex-direction:column; + } +} +@media (max-width: 1024px){ + .single-product{ + grid-template-columns:1fr; + } + .single-product yc-product-media{ + position:static; + } +} +@media (max-width: 768px){ + .single-product{ + gap:var(--gap-lg); + } + .single-product yc-product-media .gallery .slider-inner .image{ + height:70px !important; + } +} diff --git a/themes/origins/styles/component-select.scss b/themes/origins/styles/component-select.scss index acedd7e0..e58eab9f 100644 --- a/themes/origins/styles/component-select.scss +++ b/themes/origins/styles/component-select.scss @@ -8,13 +8,13 @@ yc-select { align-items: center; position: relative; - yc-select-trigger { + yc-select-trigger.yc-btn.icon { width: 100%; cursor: pointer; user-select: none; font-size: var(--text-sm); - padding: 8px 12px !important; - justify-content: space-between !important; + padding: 8px 12px; + justify-content: space-between; yc-select-value { width: 90%; @@ -39,7 +39,6 @@ yc-select { yc-select-content { display: grid; - gap: var(--gap-md); z-index: 2; width: 100%; position: absolute; @@ -65,10 +64,11 @@ yc-select { inset-block-end: calc(100% + 0.5em); } - input[type="text"] { + input[type="search"] { z-index: 2; position: sticky; inset-block-start: 0; + margin-block-end: 4px; padding: 10px 12px; color: var(--select-text-color); background-color: var(--select-bg-color); From 1450c38ff2bdbcc5b0ad5b652b5412febf5f39d5 Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Thu, 23 Jan 2025 10:18:48 +0100 Subject: [PATCH 13/14] yc-variant > add a check --- themes/origins/assets/component-variants.js | 110 ++++++++++---------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/themes/origins/assets/component-variants.js b/themes/origins/assets/component-variants.js index f54309c3..e9846395 100644 --- a/themes/origins/assets/component-variants.js +++ b/themes/origins/assets/component-variants.js @@ -1,69 +1,71 @@ -class Variant extends HTMLElement { - static observedAttributes = ["name"]; +if (!customElements.get("yc-select")) { + class Variant extends HTMLElement { + static observedAttributes = ["name"]; - constructor() { - super(); - this.VARIANT_NAME = this.getAttribute("name"); - } + constructor() { + super(); + this.VARIANT_NAME = this.getAttribute("name"); + } - connectedCallback() { - this._render(); - } + connectedCallback() { + this._render(); + } - _render() { - this.VARIANT_NAME === "upload-image" && this.uploadImage(); - } + _render() { + this.VARIANT_NAME === "upload-image" && this.uploadImage(); + } - uploadImage() { - const input = this.querySelector("input[type='file']"); - const info = this.querySelector("[data-info]"); - const unset = info.querySelector("[data-unset]"); + uploadImage() { + const input = this.querySelector("input[type='file']"); + const info = this.querySelector("[data-info]"); + const unset = info.querySelector("[data-unset]"); - const toKB = (size) => `${Math.ceil(size / 1024)} KB`; + const toKB = (size) => `${Math.ceil(size / 1024)} KB`; - const createPreview = (file, image_src) => { - info.insertAdjacentHTML( - "afterbegin", - `
- -
- ${file.name} - ${toKB(file.size)} -
-
`, - ); - }; + const createPreview = (file, image_src) => { + info.insertAdjacentHTML( + "afterbegin", + `
+ +
+ ${file.name} + ${toKB(file.size)} +
+
`, + ); + }; - const setPreview = (file, image_src, element) => { - element.querySelector("img").src = image_src; - element.querySelector(".name").textContent = file.name; - element.querySelector(".size").textContent = toKB(file.size); - }; + const setPreview = (file, image_src, element) => { + element.querySelector("img").src = image_src; + element.querySelector(".name").textContent = file.name; + element.querySelector(".size").textContent = toKB(file.size); + }; - const removePreview = () => { - info.querySelector("[data-preview]")?.remove(); - input.value = null; - }; + const removePreview = () => { + info.querySelector("[data-preview]")?.remove(); + input.value = null; + }; - input.addEventListener("change", () => { - const file = input.files?.[0]; - if (!file) return; + input.addEventListener("change", () => { + const file = input.files?.[0]; + if (!file) return; - const reader = new FileReader(); + const reader = new FileReader(); - reader.onload = () => { - const base64 = reader.result; - const preview = info.querySelector("[data-preview]"); + reader.onload = () => { + const base64 = reader.result; + const preview = info.querySelector("[data-preview]"); - !preview - ? createPreview(file, base64) - : setPreview(file, base64, preview); - unset.addEventListener("click", removePreview); - }; + !preview + ? createPreview(file, base64) + : setPreview(file, base64, preview); + unset.addEventListener("click", removePreview); + }; - reader.readAsDataURL(file); - }); + reader.readAsDataURL(file); + }); + } } -} -customElements.define("yc-variant", Variant); + customElements.define("yc-variant", Variant); +} From c27c089ccc58d3c75f94ac32f55467914f232668 Mon Sep 17 00:00:00 2001 From: Badreddine Ibril Date: Thu, 23 Jan 2025 10:40:52 +0100 Subject: [PATCH 14/14] variants > yc-upload > re-handle the upload image variant --- themes/origins/assets/component-upload.js | 71 +++++++++++++++++++ themes/origins/assets/component-variants.css | 27 +++---- themes/origins/assets/component-variants.js | 71 ------------------- themes/origins/sections/default.liquid | 2 +- themes/origins/snippets/variant-upload.liquid | 44 ++++++------ themes/origins/styles/component-variants.scss | 3 +- 6 files changed, 111 insertions(+), 107 deletions(-) create mode 100644 themes/origins/assets/component-upload.js delete mode 100644 themes/origins/assets/component-variants.js diff --git a/themes/origins/assets/component-upload.js b/themes/origins/assets/component-upload.js new file mode 100644 index 00000000..57d52dd8 --- /dev/null +++ b/themes/origins/assets/component-upload.js @@ -0,0 +1,71 @@ +if (!customElements.get("yc-upload")) { + class Upload extends HTMLElement { + static observedAttributes = ["name"]; + + constructor() { + super(); + this.input = this.querySelector("input[type='file']"); + this.info = this.querySelector("[data-info]"); + this.unset = this.info.querySelector("[data-unset]"); + } + + connectedCallback() { + this._render(); + } + + _render() { + this.onUpload(); + } + + onUpload() { + this.input.addEventListener("change", () => { + const file = this.input.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + + reader.onload = () => { + const base64 = reader.result; + const preview = this.info.querySelector("[data-preview]"); + + !preview + ? this.create(file, base64) + : this.update(file, base64, preview); + this.unset.addEventListener("click", () => this.remove()); + }; + + reader.readAsDataURL(file); + }); + } + + toKB(size) { + return `${Math.ceil(size / 1024)} KB`; + } + + create(file, image_src) { + this.info.insertAdjacentHTML( + "afterbegin", + `
+ +
+ ${file.name} + ${this.toKB(file.size)} +
+
`, + ); + } + + update(file, image_src, element) { + element.querySelector("img").src = image_src; + element.querySelector(".name").textContent = file.name; + element.querySelector(".size").textContent = this.toKB(file.size); + } + + remove() { + this.info.querySelector("[data-preview]")?.remove(); + this.input.value = null; + } + } + + customElements.define("yc-upload", Upload); +} diff --git a/themes/origins/assets/component-variants.css b/themes/origins/assets/component-variants.css index 2033a76a..916ff8c6 100644 --- a/themes/origins/assets/component-variants.css +++ b/themes/origins/assets/component-variants.css @@ -160,12 +160,13 @@ yc-variant[name=image] .image-item:has(input:disabled)::before{ yc-variant[name=image] .image-item:hover img{ opacity:0.8; } -yc-variant[name=upload-image]{ +yc-variant[name=upload-image] yc-upload{ + width:100%; display:grid; grid-gap:var(--gap-md); gap:var(--gap-md); } -yc-variant[name=upload-image] .upload-preview{ +yc-variant[name=upload-image] yc-upload .upload-preview{ display:flex; gap:var(--gap-md); flex-direction:column; @@ -179,21 +180,21 @@ yc-variant[name=upload-image] .upload-preview{ border:2px dotted color-mix(in srgb, currentColor 10%, transparent); transition:var(--transition-duration-normal); } -yc-variant[name=upload-image] .upload-preview svg{ +yc-variant[name=upload-image] yc-upload .upload-preview svg{ color:color-mix(in srgb, currentColor 60%, transparent); } -yc-variant[name=upload-image] .upload-preview img{ +yc-variant[name=upload-image] yc-upload .upload-preview img{ position:absolute; -o-object-fit:contain; object-fit:contain; translate:-50% -50%; inset:50% 0 0 50%; } -yc-variant[name=upload-image] .upload-preview:hover{ +yc-variant[name=upload-image] yc-upload .upload-preview:hover{ border-color:color-mix(in srgb, currentColor 20%, transparent); background-color:color-mix(in srgb, currentColor 5%, transparent); } -yc-variant[name=upload-image] .upload-info{ +yc-variant[name=upload-image] yc-upload .upload-info{ display:flex; gap:var(--gap-md); align-items:center; @@ -203,30 +204,30 @@ yc-variant[name=upload-image] .upload-info{ border:1px solid color-mix(in srgb, currentColor 10%, transparent); transition:var(--transition-duration-normal); } -yc-variant[name=upload-image] .upload-info:not(:has(img)){ +yc-variant[name=upload-image] yc-upload .upload-info:not(:has(img)){ opacity:0; z-index:-1; visibility:hidden; margin-block-start:-7em; } -yc-variant[name=upload-image] .upload-info .preview{ +yc-variant[name=upload-image] yc-upload .upload-info .preview{ display:flex; align-items:center; gap:var(--gap-lg); } -yc-variant[name=upload-image] .upload-info .preview img{ +yc-variant[name=upload-image] yc-upload .upload-info .preview img{ width:50px; height:50px; -o-object-fit:cover; object-fit:cover; border-radius:calc(var(--radius-md) - 4px); } -yc-variant[name=upload-image] .upload-info .preview .info{ +yc-variant[name=upload-image] yc-upload .upload-info .preview .info{ display:grid; grid-gap:var(--gap-sm); gap:var(--gap-sm); } -yc-variant[name=upload-image] .upload-info .preview .info .name{ +yc-variant[name=upload-image] yc-upload .upload-info .preview .info .name{ font-size:var(--text-md); max-width:100%; overflow:hidden; @@ -235,10 +236,10 @@ yc-variant[name=upload-image] .upload-info .preview .info .name{ white-space:nowrap; text-overflow:ellipsis; } -yc-variant[name=upload-image] .upload-info .preview .info .size{ +yc-variant[name=upload-image] yc-upload .upload-info .preview .info .size{ opacity:0.6; font-size:var(--text-sm); } -yc-variant[name=upload-image] .upload-info .actions button{ +yc-variant[name=upload-image] yc-upload .upload-info .actions button{ color:var(--variant-text-color); } diff --git a/themes/origins/assets/component-variants.js b/themes/origins/assets/component-variants.js deleted file mode 100644 index e9846395..00000000 --- a/themes/origins/assets/component-variants.js +++ /dev/null @@ -1,71 +0,0 @@ -if (!customElements.get("yc-select")) { - class Variant extends HTMLElement { - static observedAttributes = ["name"]; - - constructor() { - super(); - this.VARIANT_NAME = this.getAttribute("name"); - } - - connectedCallback() { - this._render(); - } - - _render() { - this.VARIANT_NAME === "upload-image" && this.uploadImage(); - } - - uploadImage() { - const input = this.querySelector("input[type='file']"); - const info = this.querySelector("[data-info]"); - const unset = info.querySelector("[data-unset]"); - - const toKB = (size) => `${Math.ceil(size / 1024)} KB`; - - const createPreview = (file, image_src) => { - info.insertAdjacentHTML( - "afterbegin", - `
- -
- ${file.name} - ${toKB(file.size)} -
-
`, - ); - }; - - const setPreview = (file, image_src, element) => { - element.querySelector("img").src = image_src; - element.querySelector(".name").textContent = file.name; - element.querySelector(".size").textContent = toKB(file.size); - }; - - const removePreview = () => { - info.querySelector("[data-preview]")?.remove(); - input.value = null; - }; - - input.addEventListener("change", () => { - const file = input.files?.[0]; - if (!file) return; - - const reader = new FileReader(); - - reader.onload = () => { - const base64 = reader.result; - const preview = info.querySelector("[data-preview]"); - - !preview - ? createPreview(file, base64) - : setPreview(file, base64, preview); - unset.addEventListener("click", removePreview); - }; - - reader.readAsDataURL(file); - }); - } - } - - customElements.define("yc-variant", Variant); -} diff --git a/themes/origins/sections/default.liquid b/themes/origins/sections/default.liquid index 5f677d3e..70e6efa1 100644 --- a/themes/origins/sections/default.liquid +++ b/themes/origins/sections/default.liquid @@ -1,7 +1,7 @@ {{ 'component-quantity-control.js' | asset_url | script_tag_deferred }} {{ 'component-variants.css' | asset_url | stylesheet_tag }} -{{ 'component-variants.js' | asset_url | script_tag_deferred }} +{{ 'component-upload.js' | asset_url | script_tag_deferred }} {{ 'component-select.css' | asset_url | stylesheet_tag }} {{ 'component-select.js' | asset_url | script_tag_deferred }} diff --git a/themes/origins/snippets/variant-upload.liquid b/themes/origins/snippets/variant-upload.liquid index 6b95ca51..887687ca 100644 --- a/themes/origins/snippets/variant-upload.liquid +++ b/themes/origins/snippets/variant-upload.liquid @@ -10,26 +10,28 @@ {% endcomment %} -