From f9f276533c43b681a490c0c175bd6f4192573fc6 Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Tue, 23 Feb 2021 01:12:23 +0530 Subject: [PATCH] UI/UX refresh - ongoing --- css/lighthouse.svg | 1 + css/main.css | 500 +++ css/main.min.css | 1 + css/main.scss | 447 +++ new.html | 7115 ++++++++++++++++++++++++++++++++++++++++++ old.html | 6766 +++++++++++++++++++++++++++++++++++++++ script/components.js | 4168 +++++++++++++++++++++++++ 7 files changed, 18998 insertions(+) create mode 100644 css/lighthouse.svg create mode 100644 css/main.css create mode 100644 css/main.min.css create mode 100644 css/main.scss create mode 100644 new.html create mode 100644 old.html create mode 100644 script/components.js diff --git a/css/lighthouse.svg b/css/lighthouse.svg new file mode 100644 index 0000000..25ee63c --- /dev/null +++ b/css/lighthouse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..dd7863e --- /dev/null +++ b/css/main.css @@ -0,0 +1,500 @@ +*, +::before, +::after { + padding: 0; + margin: 0; + box-sizing: border-box; + font-family: "Roboto", sans-serif; +} + +:root { + font-size: clamp(1rem, 1.2vmax, 3rem); + --ease-in-overshhot: cubic-bezier(0.6, -0.28, 0.735, 0.045); + --ease-out-overshhot: cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +body { + --accent-color: #3D5AFE; + --secondary-color: #ffac2e; + --text-color: 17, 17, 17; + --text-color-light: 100, 100, 100; + --foreground-color: 255, 255, 255; + --background-color: #efefef; + --error-color: red; + color: rgba(var(--text-color), 1); + background: url(lighthouse.svg) no-repeat; + background-size: cover; +} + +body[data-theme=dark] { + --accent-color:#657cff; + --secondary-color: #d60739; + --text-color: 240, 240, 240; + --text-color-light: 170, 170, 170; + --foreground-color: 20, 20, 20; + --error-color: rgb(255, 106, 106); +} +body[data-theme=dark] .contact-card { + margin: 0.2rem 0; + box-shadow: 0 0.1rem 0.5rem rgba(var(--text-color), 0.1); +} + +button { + position: relative; + overflow: hidden; + display: inline-flex; + border: none; + background: none; + cursor: pointer; + outline: none; + -webkit-tap-highlight-color: transparent; +} + +button:focus-visible { + outline: rgba(var(--text-color), 1) 0.1rem solid; +} + +sm-input, +sm-textarea { + --border-radius: 0.5rem; +} + +.flex { + display: flex; +} + +.grid { + display: grid; +} + +.align-center { + align-items: center; +} + +.justify-right { + margin-left: auto; +} + +.direction-column { + flex-direction: column; +} + +.space-between { + justify-content: space-between; +} + +.hide { + opacity: 0; + pointer-events: none; +} + +.hide-completely { + display: none !important; +} + +.no-transformations { + transform: none !important; +} + +.ripple { + position: absolute; + border-radius: 50%; + transform: scale(0); + background: rgba(var(--text-color), 0.2); + pointer-events: none; +} + +.interact { + position: relative; + overflow: hidden; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} + +.icon { + width: 1.5rem; + height: 1.5rem; + fill: rgba(var(--text-color), 0.7); +} + +#confirmation_popup, +#prompt_popup { + flex-direction: column; +} +#confirmation_popup h4, +#prompt_popup h4 { + font-weight: 500; + margin-bottom: 0.5rem; +} +#confirmation_popup sm-button, +#prompt_popup sm-button { + margin: 0; +} +#confirmation_popup .flex, +#prompt_popup .flex { + padding: 0; + margin-top: 1rem; +} +#confirmation_popup .flex sm-button:first-of-type, +#prompt_popup .flex sm-button:first-of-type { + margin-right: 0.6rem; + margin-left: auto; +} + +.popup__header { + padding: 0.5rem 1.5rem 0 1rem; + display: grid; + grid-template-columns: auto 1fr; + gap: 0.5rem; + align-items: center; + width: 100%; +} + +.popup__header__close { + padding: 0.5rem; + cursor: pointer; +} + +.popup__header__title { + font-size: 1.1rem; + font-weight: 500; + color: rgba(var(--text-color), 0.8); +} + +.close-icon { + height: 2rem; + width: 2rem; +} + +.home_page__section { + backdrop-filter: blur(1rem); +} +.home_page__section--left { + background-color: rgba(var(--foreground-color), 0.8); +} +.home_page__section--right { + overflow: hidden; + background-color: rgba(var(--foreground-color), 0.9); +} + +.home_page__header { + gap: 0.2rem 1rem; +} +.home_page__header--left { + padding: 1.5rem 0.7rem 1.5rem 1rem; + grid-template-columns: 1fr auto; + grid-template-areas: "company setting" "app setting"; +} +.home_page__header--right { + padding: 1.5rem 1rem; +} + +.header__company-name { + grid-area: company; + font-weight: 500; + color: rgba(var(--text-color), 0.7); + align-self: flex-end; +} + +.header__app-name { + align-self: flex-start; + line-height: 1; + grid-area: app; + font-weight: 700; + color: rgba(var(--text-color), 0.9); +} + +.header__settings-button { + grid-area: setting; + padding: 0.8rem; +} + +.header__settings-button__icon { + transform: rotate(90deg); +} + +.home_page__nav { + padding: 1.5rem 1rem; + gap: 1rem; + grid-template-columns: repeat(3, 1fr); + justify-items: center; +} + +.nav-button { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + width: 4rem; +} + +.nav-button__icon { + height: 3rem; + width: 3rem; + padding: 0.8rem; + border-radius: 0.8rem; + margin-bottom: 0.8rem; + fill: var(--accent-color); + background: rgba(var(--foreground-color), 1); +} + +.nav-button__name { + font-size: 0.9rem; + font-weight: 500; + color: rgba(var(--text-color), 0.7); +} + +.fab { + position: absolute; + display: inline-flex; + border-radius: 5rem; + aspect-ratio: 1/1; + padding: 0.7rem; + background: #FF1F1F; + box-shadow: 0 0.5rem 0.5rem rgba(0, 0, 0, 0.16); +} + +.fab__icon { + height: 1.8rem; + width: 1.8rem; + fill: white; +} + +#base_header { + width: 100%; +} + +.header__title { + font-weight: 500; + color: rgba(var(--text-color), 0.7); +} + +#search_contacts { + width: 14rem; + --padding: 0.5rem 0.6rem; + --background: rgba(var(--text-color), 0.1); +} + +.search__icon { + height: 1.2rem; +} + +#selected_contact_options { + margin-top: 0.5rem; + list-style: none; +} + +.contact-option:not(:last-of-type) { + margin-right: 0.5rem; +} +.contact-option:first-of-type { + padding-right: 0.6rem; + margin-right: auto; + margin-left: -0.6rem; +} + +.contact-option__button { + display: inline-flex; + align-items: center; + border-radius: 0.3rem; + padding: 0.5rem 0.6rem; +} + +.contact-option__icon { + height: 1.2rem; + width: 1.2rem; + margin-right: 0.5rem; +} + +.contact-option__name { + font-weight: 500; + font-size: 0.9rem; + color: rgba(var(--text-color), 0.8); +} + +#contacts_container { + display: grid; + gap: 1rem; + height: 100%; + align-content: flex-start; + overflow-y: auto; + list-style: none; + padding: 0 1rem 6rem 1rem; + grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); +} + +.contact-card { + position: relative; + overflow: hidden; + display: flex; + width: 100%; + flex-direction: column; + padding: 1rem; + min-height: 8rem; + cursor: pointer; + align-self: flex-start; + justify-content: center; + border-radius: 0.5rem; + background: rgba(var(--foreground-color), 1); + box-shadow: 0 0.2rem 0.4rem rgba(0, 0, 0, 0.06); +} + +.contact-card__checkbox { + --border-radius: 0.5rem; + --border-color: rgba(var(--text-color), 0.6); + position: absolute; + top: 0; + right: 0; + margin: 1rem; +} + +.contact-card__initial { + width: 3rem; + height: 3rem; + display: flex; + padding: 1.5rem; + font-size: 1.2rem; + text-align: center; + user-select: none; + border-radius: 1rem; + margin-bottom: 1.5rem; + align-items: center; + justify-content: center; + background-color: rgba(var(--text-color), 0.1); +} + +.contact-card__name { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: 500; + font-size: 1rem; + color: rgba(var(--text-color), 0.8); +} + +.contact__grid { + gap: 1rem; + grid-template-columns: 1fr auto; +} + +.contact-card__more { + padding: 0.2rem; +} + +#add_contact_button { + right: 0; + bottom: 0; + margin: 1.5rem; +} + +@media all and (max-width: 640px) { + #main_card { + min-height: 100vh; + } + + .fab { + position: fixed; + } + + #home_page { + height: 100%; + grid-template-rows: auto 1fr; + } + + .home_page__section--right { + align-content: flex-start; + } + + .home_page__nav { + padding: 0.5rem 1rem 2rem 1rem; + grid-template-columns: repeat(4, 1fr); + } +} +@media all and (min-width: 640px) { + body { + place-content: center; + min-height: 100vh; + } + + sm-popup { + --width: 24rem; + } + + .popup__header { + padding: 1rem 1.5rem 0 1rem; + } + + #main_card { + width: 80vw; + height: 80vh; + border-radius: 0.5rem; + box-shadow: 0 1.5rem 1.5rem rgba(0, 0, 0, 0.16); + } + + #home_page { + height: 100%; + overflow-y: auto; + grid-template-columns: 20rem 1fr; + } + + .home_page__section--left { + border-radius: 0.5rem 0 0 0.5rem; + } + .home_page__section--right { + grid-template-rows: auto 1fr; + border-radius: 0 0.5rem 0.5rem 0; + box-shadow: -0.5rem 0 1rem rgba(0, 0, 0, 0.06); + } + + .home_page__header--left { + padding: 1.8rem 1.7rem 2rem 2.5rem; + } + .home_page__header--right { + padding: 2rem 2.5rem; + } + + .home_page__nav { + padding: 0 2.5rem; + } + + #contacts_container { + padding: 0 2.5rem 8rem 2.5rem; + } + + #add_contact_button { + right: 0; + bottom: 0; + margin: 2rem 2.5rem; + } +} +@media all and (min-width: 1280px) { + #main_card { + width: 65vw; + height: 85vh; + } +} +@media all and (min-width: 1920px) { + #main_card { + width: 65vw; + height: 70vh; + } +} +@media (any-hover: hover) { + .contact-card__checkbox:not([checked]):not(:focus-within), +.contact-card__more:not(:focus-within) { + opacity: 0; + transition: opacity 0.3s; + } + + .contact-card:hover .contact-card__checkbox, +.contact-card:hover .contact-card__more { + opacity: 1; + } + + .contact-option__button { + transition: background 0.3s; + } + .contact-option__button:hover { + background-color: rgba(var(--text-color), 0.1); + } +} \ No newline at end of file diff --git a/css/main.min.css b/css/main.min.css new file mode 100644 index 0000000..2174cba --- /dev/null +++ b/css/main.min.css @@ -0,0 +1 @@ +.interact,button{-webkit-tap-highlight-color:transparent;cursor:pointer;overflow:hidden}*,::after,::before{padding:0;margin:0;box-sizing:border-box;font-family:Roboto,sans-serif}:root{font-size:clamp(1rem,1.2vmax,3rem);--ease-in-overshhot:cubic-bezier(0.6, -0.28, 0.735, 0.045);--ease-out-overshhot:cubic-bezier(0.175, 0.885, 0.32, 1.275)}body{--accent-color:#3D5AFE;--secondary-color:#ffac2e;--text-color:17,17,17;--text-color-light:100,100,100;--foreground-color:255,255,255;--background-color:#efefef;--error-color:red;color:rgba(var(--text-color),1);background:url(lighthouse.svg) no-repeat;background-size:cover}body[data-theme=dark]{--accent-color:#657cff;--secondary-color:#d60739;--text-color:240,240,240;--text-color-light:170,170,170;--foreground-color:20,20,20;--error-color:rgb(255, 106, 106)}body[data-theme=dark] .contact-card{margin:.2rem 0;box-shadow:0 .1rem .5rem rgba(var(--text-color),.1)}button{position:relative;display:inline-flex;border:none;background:0 0;outline:0}.fab,.ripple{position:absolute}button:focus-visible{outline:solid rgba(var(--text-color),1)}sm-input,sm-textarea{--border-radius:0.5rem}.flex{display:flex}.grid{display:grid}.align-center{align-items:center}.justify-right{margin-left:auto}.direction-column{flex-direction:column}.space-between{justify-content:space-between}.hide{opacity:0;pointer-events:none}.hide-completely{display:none!important}.no-transformations{transform:none!important}.ripple{border-radius:50%;transform:scale(0);background:rgba(var(--text-color),.2);pointer-events:none}.interact{position:relative}.icon{width:1.5rem;height:1.5rem;fill:rgba(var(--text-color),.7)}#confirmation_popup,#prompt_popup{flex-direction:column}#confirmation_popup h4,#prompt_popup h4{font-weight:500;margin-bottom:.5rem}#confirmation_popup sm-button,#prompt_popup sm-button{margin:0}#confirmation_popup .flex,#prompt_popup .flex{padding:0;margin-top:1rem}#confirmation_popup .flex sm-button:first-of-type,#prompt_popup .flex sm-button:first-of-type{margin-right:.6rem;margin-left:auto}.popup__header{padding:.5rem 1.5rem 0 1rem;display:grid;grid-template-columns:auto 1fr;gap:.5rem;align-items:center;width:100%}.popup__header__close{padding:.5rem;cursor:pointer}.popup__header__title{font-size:1.1rem;font-weight:500;color:rgba(var(--text-color),.8)}.header__company-name,.header__title,.nav-button__name{color:rgba(var(--text-color),.7)}.close-icon{height:2rem;width:2rem}.home_page__section{backdrop-filter:blur(1rem)}.home_page__section--left{background-color:rgba(var(--foreground-color),.8)}.home_page__section--right{overflow:hidden;background-color:rgba(var(--foreground-color),.9)}.contact-card,.nav-button__icon{background:rgba(var(--foreground-color),1)}.home_page__header{gap:.2rem 1rem}.home_page__header--left{padding:1.5rem .7rem 1.5rem 1rem;grid-template-columns:1fr auto;grid-template-areas:"company setting" "app setting"}.home_page__header--right{padding:1.5rem 1rem}.header__company-name{grid-area:company;font-weight:500;align-self:flex-end}.header__app-name{align-self:flex-start;line-height:1;grid-area:app;font-weight:700;color:rgba(var(--text-color),.9)}.header__settings-button{grid-area:setting;padding:.8rem}.header__settings-button__icon{transform:rotate(90deg)}.home_page__nav{padding:1.5rem 1rem;gap:1rem;grid-template-columns:repeat(3,1fr);justify-items:center}.nav-button{display:flex;flex-direction:column;align-items:center;text-align:center;width:4rem}.nav-button__icon{height:3rem;width:3rem;padding:.8rem;border-radius:.8rem;margin-bottom:.8rem;fill:var(--accent-color)}.nav-button__name{font-size:.9rem;font-weight:500}.fab{display:inline-flex;border-radius:5rem;aspect-ratio:1/1;padding:.7rem;background:#FF1F1F;box-shadow:0 .5rem .5rem rgba(0,0,0,.16)}.fab__icon{height:1.8rem;width:1.8rem;fill:#fff}#base_header{width:100%}.header__title{font-weight:500}.contact-card__name,.contact-option__name{font-weight:500;color:rgba(var(--text-color),.8)}#search_contacts{width:14rem;--padding:0.5rem 0.6rem;--background:rgba(var(--text-color), 0.1)}.search__icon{height:1.2rem}#selected_contact_options{margin-top:.5rem;list-style:none}.contact-option:not(:last-of-type){margin-right:.5rem}.contact-option:first-of-type{padding-right:.6rem;margin-right:auto;margin-left:-.6rem}.contact-option__button{display:inline-flex;align-items:center;border-radius:.3rem;padding:.5rem .6rem}.contact-option__icon{height:1.2rem;width:1.2rem;margin-right:.5rem}.contact-option__name{font-size:.9rem}#contacts_container{display:grid;gap:1rem;height:100%;align-content:flex-start;overflow-y:auto;list-style:none;padding:0 1rem 6rem;grid-template-columns:repeat(auto-fill,minmax(12rem,1fr))}.contact-card{position:relative;overflow:hidden;display:flex;width:100%;flex-direction:column;padding:1rem;min-height:8rem;cursor:pointer;align-self:flex-start;justify-content:center;border-radius:.5rem;box-shadow:0 .2rem .4rem rgba(0,0,0,.06)}.contact-card__checkbox{--border-radius:0.5rem;--border-color:rgba(var(--text-color), 0.6);position:absolute;top:0;right:0;margin:1rem}.contact-card__initial{width:3rem;height:3rem;display:flex;padding:1.5rem;font-size:1.2rem;text-align:center;user-select:none;border-radius:1rem;margin-bottom:1.5rem;align-items:center;justify-content:center;background-color:rgba(var(--text-color),.1)}.contact-card__name{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:1rem}.contact__grid{gap:1rem;grid-template-columns:1fr auto}.contact-card__more{padding:.2rem}#add_contact_button{right:0;bottom:0;margin:1.5rem}@media all and (max-width:640px){#main_card{min-height:100vh}.fab{position:fixed}#home_page{height:100%;grid-template-rows:auto 1fr}.home_page__section--right{align-content:flex-start}.home_page__nav{padding:.5rem 1rem 2rem;grid-template-columns:repeat(4,1fr)}}@media all and (min-width:640px){body{place-content:center;min-height:100vh}sm-popup{--width:24rem}.popup__header{padding:1rem 1.5rem 0 1rem}#main_card{width:80vw;height:80vh;border-radius:.5rem;box-shadow:0 1.5rem 1.5rem rgba(0,0,0,.16)}#home_page{height:100%;overflow-y:auto;grid-template-columns:20rem 1fr}.home_page__section--left{border-radius:.5rem 0 0 .5rem}.home_page__section--right{grid-template-rows:auto 1fr;border-radius:0 .5rem .5rem 0;box-shadow:-.5rem 0 1rem rgba(0,0,0,.06)}.home_page__header--left{padding:1.8rem 1.7rem 2rem 2.5rem}.home_page__header--right{padding:2rem 2.5rem}.home_page__nav{padding:0 2.5rem}#contacts_container{padding:0 2.5rem 8rem}#add_contact_button{right:0;bottom:0;margin:2rem 2.5rem}}@media all and (min-width:1280px){#main_card{width:65vw;height:85vh}}@media all and (min-width:1920px){#main_card{width:65vw;height:70vh}}@media (any-hover:hover){.contact-card__checkbox:not([checked]):not(:focus-within),.contact-card__more:not(:focus-within){opacity:0;transition:opacity .3s}.contact-card:hover .contact-card__checkbox,.contact-card:hover .contact-card__more{opacity:1}.contact-option__button{transition:background .3s}.contact-option__button:hover{background-color:rgba(var(--text-color),.1)}} \ No newline at end of file diff --git a/css/main.scss b/css/main.scss new file mode 100644 index 0000000..e8ae55d --- /dev/null +++ b/css/main.scss @@ -0,0 +1,447 @@ +*, +::before, +::after{ + padding: 0; + margin: 0; + box-sizing: border-box; + font-family: 'Roboto', sans-serif; +} +:root{ + font-size: clamp(1rem, 1.2vmax, 3rem); + --ease-in-overshhot: cubic-bezier(0.6, -0.28, 0.735, 0.045); + --ease-out-overshhot: cubic-bezier(0.175, 0.885, 0.32, 1.275); +} +body{ + --accent-color: #3D5AFE; + --secondary-color: #ffac2e; + --text-color: 17, 17, 17; + --text-color-light: 100, 100, 100; + --foreground-color: 255, 255, 255; + --background-color: #efefef; + --error-color: red; + color: rgba(var(--text-color), 1); + background: url(lighthouse.svg) no-repeat; + background-size: cover; +} +body[data-theme='dark']{ + --accent-color:#657cff; + --secondary-color: #d60739; + --text-color: 240, 240, 240; + --text-color-light: 170, 170, 170; + --foreground-color: 20, 20, 20; + --error-color: rgb(255, 106, 106); + .contact-card{ + margin: 0.2rem 0; + box-shadow: 0 0.1rem 0.5rem rgba(var(--text-color), 0.1); + } +} +button{ + position: relative; + overflow: hidden; + display: inline-flex; + border: none; + background: none; + cursor: pointer; + outline: none; + -webkit-tap-highlight-color: transparent; +} +button:focus-visible{ + outline: rgba(var(--text-color), 1) 0.1rem solid; +} +sm-input, +sm-textarea{ + --border-radius: 0.5rem; +} +.flex{ + display: flex; +} +.grid{ + display: grid; +} +.align-center{ + align-items: center; +} +.justify-right{ + margin-left: auto; +} +.direction-column{ + flex-direction: column; +} +.space-between{ + justify-content: space-between; +} +.hide{ + opacity: 0; + pointer-events: none; +} +.hide-completely{ + display: none !important; +} +.no-transformations{ + transform: none !important; +} +.ripple{ + position: absolute; + border-radius: 50%; + transform: scale(0); + background: rgba(var(--text-color), 0.2); + pointer-events: none; +} +.interact{ + position: relative; + overflow: hidden; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} +.icon{ + width: 1.5rem; + height: 1.5rem; + fill: rgba(var(--text-color), 0.7); +} +#confirmation_popup, +#prompt_popup { + flex-direction: column; + h4 { + font-weight: 500; + margin-bottom: 0.5rem; + } + sm-button{ + margin: 0; + } + .flex { + padding: 0; + margin-top: 1rem; + sm-button:first-of-type { + margin-right: 0.6rem; + margin-left: auto; + } + } +} +.popup__header{ + padding: 0.5rem 1.5rem 0 1rem; + display: grid; + grid-template-columns: auto 1fr; + gap: 0.5rem; + align-items: center; + width: 100%; +} +.popup__header__close{ + padding: 0.5rem; + cursor: pointer; +} +.popup__header__title{ + font-size: 1.1rem; + font-weight: 500; + color: rgba(var(--text-color), 0.8); +} +.close-icon{ + height: 2rem; + width: 2rem; +} +#main_card{ + +} +.home_page__section{ + backdrop-filter: blur(1rem); + &--left{ + background-color: rgba(var(--foreground-color), 0.8); + } + &--right{ + overflow: hidden; + background-color: rgba(var(--foreground-color), 0.9); + } +} +.home_page__header{ + gap: 0.2rem 1rem; + &--left{ + padding: 1.5rem 0.7rem 1.5rem 1rem; + grid-template-columns: 1fr auto; + grid-template-areas: 'company setting' 'app setting'; + } + &--right{ + padding: 1.5rem 1rem; + } +} +.header__company-name{ + grid-area: company; + font-weight: 500; + color: rgba(var(--text-color), 0.7); + align-self: flex-end; +} +.header__app-name{ + align-self: flex-start; + line-height: 1; + grid-area: app; + font-weight: 700; + color: rgba(var(--text-color), 0.9); +} +.header__settings-button{ + grid-area: setting; + padding: 0.8rem; +} +.header__settings-button__icon{ + transform: rotate(90deg); +} +.home_page__nav{ + padding: 1.5rem 1rem; + gap: 1rem; + grid-template-columns: repeat(3, 1fr); + justify-items: center; +} + +.nav-button{ + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + width: 4rem; +} + +.nav-button__icon{ + height: 3rem; + width: 3rem; + padding: 0.8rem; + border-radius: 0.8rem; + margin-bottom: 0.8rem; + fill: var(--accent-color); + background: rgba(var(--foreground-color), 1); +} +.nav-button__name{ + font-size: 0.9rem; + font-weight: 500; + color: rgba(var(--text-color), 0.7); +} + +.fab{ + position: absolute; + display: inline-flex; + border-radius: 5rem; + aspect-ratio: 1/1; + padding: 0.7rem; + background: #FF1F1F; + box-shadow: 0 0.5rem 0.5rem rgba(0, 0, 0, 0.16); +} +.fab__icon{ + height: 1.8rem; + width: 1.8rem; + fill: white; +} + +#base_header{ + width: 100%; +} +.header__title{ + font-weight: 500; + color: rgba(var(--text-color), 0.7); +} +#search_contacts{ + width: 14rem; + --padding: 0.5rem 0.6rem; + --background: rgba(var(--text-color), 0.1); +} +.search__icon{ + height: 1.2rem; +} +#selected_contact_options{ + margin-top: 0.5rem; + list-style: none; +} +.contact-option{ + &:not(:last-of-type){ + margin-right: 0.5rem; + } + &:first-of-type{ + padding-right: 0.6rem; + margin-right: auto; + margin-left: -0.6rem; + } +} +.contact-option__button{ + display: inline-flex; + align-items: center; + border-radius: 0.3rem; + padding: 0.5rem 0.6rem; +} +.contact-option__icon{ + height: 1.2rem; + width: 1.2rem; + margin-right: 0.5rem; +} +.contact-option__name{ + font-weight: 500; + font-size: 0.9rem; + color: rgba(var(--text-color), 0.8); +} + +#contacts_container{ + display: grid; + gap: 1rem; + height: 100%; + align-content: flex-start; + overflow-y: auto; + list-style: none; + padding: 0 1rem 6rem 1rem; + grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); +} +.contact-card{ + position: relative; + overflow: hidden; + display: flex; + width: 100%; + flex-direction: column; + padding: 1rem; + min-height: 8rem; + cursor: pointer; + align-self: flex-start; + justify-content: center; + border-radius: 0.5rem; + background: rgba(var(--foreground-color), 1); + box-shadow: 0 0.2rem 0.4rem rgba(0, 0, 0, 0.06); +} +.contact-card__checkbox{ + --border-radius: 0.5rem; + --border-color: rgba(var(--text-color), 0.6); + position: absolute; + top: 0; + right: 0; + margin: 1rem; +} +.contact-card__initial{ + width: 3rem; + height: 3rem; + display: flex; + padding: 1.5rem; + font-size: 1.2rem; + text-align: center; + user-select: none; + border-radius: 1rem; + margin-bottom: 1.5rem; + align-items: center; + justify-content: center; + background-color: rgba(var(--text-color), 0.1); +} +.contact-card__name{ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: 500; + font-size: 1rem; + color: rgba(var(--text-color), 0.8); +} +.contact__grid{ + gap: 1rem; + grid-template-columns: 1fr auto; +} +.contact-card__more{ + padding: 0.2rem; +} + +#add_contact_button{ + right: 0; + bottom: 0; + margin: 1.5rem; +} + +// mobile only styles +@media all and (max-width: 640px){ + #main_card{ + min-height: 100vh; + } + .fab{ + position: fixed; + } + #home_page{ + height: 100%; + grid-template-rows: auto 1fr; + } + .home_page__section--right{ + align-content: flex-start; + } + .home_page__nav{ + padding: 0.5rem 1rem 2rem 1rem; + grid-template-columns: repeat(4, 1fr); + } +} + +// desktop only styles +@media all and (min-width: 640px){ + body{ + place-content: center; + min-height: 100vh; + } + sm-popup{ + --width: 24rem; + } + .popup__header{ + padding: 1rem 1.5rem 0 1rem; + } + #main_card{ + width: 80vw; + height: 80vh; + border-radius: 0.5rem; + box-shadow: 0 1.5rem 1.5rem rgba(0, 0, 0, 0.16); + } + #home_page{ + height: 100%; + overflow-y: auto; + grid-template-columns: 20rem 1fr; + } + .home_page__section{ + &--left{ + border-radius: 0.5rem 0 0 0.5rem; + } + &--right{ + grid-template-rows: auto 1fr; + border-radius: 0 0.5rem 0.5rem 0; + box-shadow: -0.5rem 0 1rem rgba(0, 0, 0, 0.06); + } + } + .home_page__header{ + &--left{ + padding: 1.8rem 1.7rem 2rem 2.5rem; + } + &--right{ + padding: 2rem 2.5rem; + } + } + .home_page__nav{ + padding: 0 2.5rem; + } + #contacts_container{ + padding: 0 2.5rem 8rem 2.5rem; + } + #add_contact_button{ + right: 0; + bottom: 0; + margin: 2rem 2.5rem; + } +} +@media all and (min-width: 1280px){ + #main_card{ + width: 65vw; + height: 85vh; + } +} +@media all and (min-width: 1920px){ + #main_card{ + width: 65vw; + height: 70vh; + } +} +@media (any-hover: hover){ + .contact-card__checkbox:not([checked]):not(:focus-within), + .contact-card__more:not(:focus-within){ + opacity: 0; + transition: opacity 0.3s; + } + .contact-card:hover .contact-card__checkbox, + .contact-card:hover .contact-card__more{ + opacity: 1; + } + .contact-option__button{ + transition: background 0.3s; + &:hover{ + background-color: rgba(var(--text-color), 0.1); + } + } +} \ No newline at end of file diff --git a/new.html b/new.html new file mode 100644 index 0000000..e1c2dac --- /dev/null +++ b/new.html @@ -0,0 +1,7115 @@ + + + + + + + new web wallet + + + + + + + + +

+

+
+ Cancel + OK +
+
+ +

+

+ +
+ Cancel + OK +
+
+ + + + + +

0

+ + Check +
+ + +

S

+

Sairaj Mote

+ +
+ + + + + + + + Send + + + + +
+
+
+
+
RanhiMall
+

FLO Web Wallet

+ +
+ +
+
+
+
+

Saved addresses

+ + + +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
    +
  • + +

    S

    +
    +

    Sairaj

    + +
    +
  • +
+ +
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/old.html b/old.html new file mode 100644 index 0000000..9e35a94 --- /dev/null +++ b/old.html @@ -0,0 +1,6766 @@ + + + + + FLO web wallet + + + + + + + +
+
+ There seems to be a problem connecting to the internet. +
+
+ +
+
+ +
+ + + + + + + +
Generate
+ Generate address +
+
+ + + + + + +
Send
+ Send FLO data +
+
+ + + +
Monitor
+ Monitor FLO data +
+
+ + Settings + + + + + +
Settings
+ Settings +
+
+
+
+ Copied +
+
+
+
+

Add new address to monitoring list

+
+ + +
+
+ + +
+ + +
+
+
+
+

Edit display card

+ + Copy FLO address + + +
+ + + + + + + +

+
+
+ + +
+ + + +
+
+
+
+ + Add new address to monitor + + +
+
+
+ + Go back to monitoring page + + + +

+
+
+ + Refresh transactions + + +
+ + + + + + +
+ +
+
+
+ + + + + + +
Available balance
+ 0 + FLO(s) +
To send FLO data, make sure you have enough balance.
+
+
+ + + +
+
+
+
+ + +
+
+ + +
+
+ + +
1040/1040
+
+ +
+
+
+
+ + Go back to monitoring page + + + +

Send

+
+
+
+
+ + +
+ +
+
+
+ + + + + + +

Transaction successful

+

+ +
+
+ +

+
+
+
+
+
+

Dark mode

+
+ + Automatic
+ Dark mode active : 6pm - 6am +
+ +
+
+ Manual
+ Dark mode +
+ +
+
+
+

Clear all local data

+
This will delete all local Web Wallet data like added addresses and locally stored + transactions.After clearing local data you may experience slow loading of newly added address, + please proceed cautiously!
+ +
+
+

About

+
Version 2.7.4
+ +
Powered by
+ + + + + + + + + + + + + +
+
+
+
+
+
+ +
+ + + + + + + + + \ No newline at end of file diff --git a/script/components.js b/script/components.js new file mode 100644 index 0000000..e366405 --- /dev/null +++ b/script/components.js @@ -0,0 +1,4168 @@ +//Button +const smButton = document.createElement('template') +smButton.innerHTML = ` + +`; +customElements.define('sm-button', + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smButton.content.cloneNode(true)) + } + + get disabled() { + return this.isDisabled + } + + set disabled(value) { + if (value && !this.isDisabled) { + this.isDisabled = true + this.setAttribute('disable', '') + this.button.removeAttribute('tabindex') + } else if (!value && this.isDisabled) { + this.isDisabled = false + this.removeAttribute('disable') + } + } + + dispatch() { + if (this.isDisabled) { + this.dispatchEvent(new CustomEvent('disable', { + bubbles: true, + composed: true + })) + } else { + this.dispatchEvent(new CustomEvent('clicked', { + bubbles: true, + composed: true + })) + } + } + + connectedCallback() { + this.isDisabled = false + this.button = this.shadowRoot.querySelector('.button') + if (this.hasAttribute('disable') && !this.isDisabled) + this.isDisabled = true + this.addEventListener('click', (e) => { + this.dispatch() + }) + } + }) + +//Input +const smInput = document.createElement('template') +smInput.innerHTML = ` + +
+ + +
+`; +customElements.define('sm-input', + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smInput.content.cloneNode(true)) + } + static get observedAttributes() { + return ['placeholder'] + } + + get value() { + return this.shadowRoot.querySelector('input').value + } + + set value(val) { + this.shadowRoot.querySelector('input').value = val; + this.checkInput() + this.fireEvent() + } + + get placeholder() { + return this.getAttribute('placeholder') + } + + set placeholder(val) { + this.setAttribute('placeholder', val) + } + + get type() { + return this.getAttribute('type') + } + + get isValid() { + return this.shadowRoot.querySelector('input').checkValidity() + } + + get validity() { + return this.shadowRoot.querySelector('input').validity + } + + set disabled(value) { + if (value) + this.shadowRoot.querySelector('.input').classList.add('disabled') + else + this.shadowRoot.querySelector('.input').classList.remove('disabled') + } + set readOnly(value) { + if (value) { + this.shadowRoot.querySelector('input').setAttribute('readonly', '') + this.shadowRoot.querySelector('.input').classList.add('readonly') + } else { + this.shadowRoot.querySelector('input').removeAttribute('readonly') + this.shadowRoot.querySelector('.input').classList.remove('readonly') + } + } + + setValidity = (message) => { + this.feedbackText.textContent = message + } + + showValidity = () => { + this.feedbackText.classList.remove('hide-completely') + } + + hideValidity = () => { + this.feedbackText.classList.add('hide-completely') + } + + focusIn = () => { + this.input.focus() + } + + focusOut = () => { + this.input.blur() + } + + fireEvent = () => { + let event = new Event('input', { + bubbles: true, + cancelable: true, + composed: true + }); + this.dispatchEvent(event); + } + + checkInput = (e) => { + if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder') === '') return; + if (this.input.value !== '') { + if (this.animate) + this.inputParent.classList.add('animate-label') + else + this.label.classList.add('hide') + if (!this.readonly) + this.clearBtn.classList.remove('hide') + } else { + if (this.animate) + this.inputParent.classList.remove('animate-label') + else + this.label.classList.remove('hide') + if (!this.readonly) + this.clearBtn.classList.add('hide') + } + } + + + connectedCallback() { + this.inputParent = this.shadowRoot.querySelector('.input') + this.clearBtn = this.shadowRoot.querySelector('.clear') + this.label = this.shadowRoot.querySelector('.label') + this.feedbackText = this.shadowRoot.querySelector('.feedback-text') + this.valueChanged = false; + this.readonly = false + this.isNumeric = false + this.min + this.max + this.animate = this.hasAttribute('animate') + this.input = this.shadowRoot.querySelector('input') + this.shadowRoot.querySelector('.label').textContent = this.getAttribute('placeholder') + if (this.hasAttribute('value')) { + this.input.value = this.getAttribute('value') + this.checkInput() + } + if (this.hasAttribute('required')) { + this.input.setAttribute('required', '') + } + if (this.hasAttribute('min')) { + let minValue = this.getAttribute('min') + this.input.setAttribute('min', minValue) + this.min = parseInt(minValue) + } + if (this.hasAttribute('max')) { + let maxValue = this.getAttribute('max') + this.input.setAttribute('max', maxValue) + this.max = parseInt(maxValue) + } + if (this.hasAttribute('minlength')) { + let minValue = this.getAttribute('minlength') + this.input.setAttribute('minlength', minValue) + } + if (this.hasAttribute('maxlength')) { + let maxValue = this.getAttribute('maxlength') + this.input.setAttribute('maxlength', maxValue) + } + if (this.hasAttribute('pattern')) { + this.input.setAttribute('pattern', this.getAttribute('pattern')) + } + if (this.hasAttribute('readonly')) { + this.input.setAttribute('readonly', '') + this.readonly = true + } + if (this.hasAttribute('disabled')) { + this.inputParent.classList.add('disabled') + } + if (this.hasAttribute('error-text')) { + this.feedbackText.textContent = this.getAttribute('error-text') + } + if (this.hasAttribute('type')) { + if (this.getAttribute('type') === 'number') { + this.input.setAttribute('inputmode', 'numeric') + this.input.setAttribute('type', 'number') + this.isNumeric = true + } else + this.input.setAttribute('type', this.getAttribute('type')) + } else + this.input.setAttribute('type', 'text') + this.input.addEventListener('input', e => { + this.checkInput(e) + }) + this.clearBtn.addEventListener('click', e => { + this.value = '' + }) + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + if (name === 'placeholder') { + this.shadowRoot.querySelector('.label').textContent = newValue; + this.setAttribute('aria-label', newValue); + } + } + } + }) + +//textarea +const smTextarea = document.createElement('template') +smTextarea.innerHTML = ` + + +`; +customElements.define('sm-textarea', + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smTextarea.content.cloneNode(true)) + this.textarea = this.shadowRoot.querySelector('textarea') + } + get value() { + return this.textarea.value + } + set value(val) { + this.textarea.value = val; + this.textareaBox.dataset.value = val + this.checkInput() + this.fireEvent() + } + focusIn = () => { + this.textarea.focus() + } + fireEvent() { + let event = new Event('input', { + bubbles: true, + cancelable: true, + composed: true + }); + this.dispatchEvent(event); + } + checkInput = () => { + if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder') === '') + return; + if (this.textarea.value !== '') { + this.placeholder.classList.add('hide') + } else { + this.placeholder.classList.remove('hide') + } + } + connectedCallback() { + this.textareaBox = this.shadowRoot.querySelector('.textarea') + this.placeholder = this.shadowRoot.querySelector('.placeholder') + + if(this.hasAttribute('placeholder')) + this.placeholder.textContent = this.getAttribute('placeholder') + + if (this.hasAttribute('value')) { + this.textarea.value = this.getAttribute('value') + this.checkInput() + } + if (this.hasAttribute('required')) { + this.textarea.setAttribute('required', '') + } + if (this.hasAttribute('readonly')) { + this.textarea.setAttribute('readonly', '') + } + if (this.hasAttribute('rows')) { + this.textarea.setAttribute('rows', this.getAttribute('rows')) + } + this.textarea.addEventListener('input', e => { + this.textareaBox.dataset.value = this.textarea.value + this.checkInput() + }) + } + }) + +// tab +const smTab = document.createElement('template') +smTab.innerHTML = ` + +
+ +
+`; + +customElements.define('sm-tab', class extends HTMLElement { + constructor() { + super() + this.shadow = this.attachShadow({ + mode: 'open' + }).append(smTab.content.cloneNode(true)) + } +}) + +//chcekbox + +const smCheckbox = document.createElement('template') +smCheckbox.innerHTML = ` + +` +customElements.define('sm-checkbox', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smCheckbox.content.cloneNode(true)) + + this.checkbox = this.shadowRoot.querySelector('.checkbox'); + this.input = this.shadowRoot.querySelector('input') + + this.isChecked = false + this.isDisabled = false + } + + static get observedAttributes() { + return ['disabled', 'checked'] + } + + get disabled() { + return this.getAttribute('disabled') + } + + set disabled(val) { + this.setAttribute('disabled', val) + } + + get checked() { + return this.isChecked + } + + set checked(value) { + if (value) { + this.setAttribute('checked', '') + } + else { + this.removeAttribute('checked') + } + } + + set value(val) { + this.val = val + this.setAttribute('value', value) + } + + get value() { + return getAttribute('value') + } + + dispatch = () => { + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true + })) + } + handleKeyup = e => { + if ((e.code === "Enter" || e.code === "Space") && this.isDisabled == false) { + if (this.hasAttribute('checked')) { + this.input.checked = false + this.removeAttribute('checked') + } + else { + this.input.checked = true + this.setAttribute('checked', '') + } + } + } + handleChange = e => { + if (this.input.checked) { + this.setAttribute('checked', '') + } + else { + this.removeAttribute('checked') + } + } + + connectedCallback() { + this.val = '' + this.addEventListener('keyup', this.handleKeyup) + this.input.addEventListener('change', this.handleChange) + } + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + if (name === 'disabled') { + if (newValue === 'true') { + this.checkbox.classList.add('disabled') + this.isDisabled = true + } else { + this.checkbox.classList.remove('disabled') + this.isDisabled = false + } + } + else if (name === 'checked') { + if (this.hasAttribute('checked')) { + this.isChecked = true + this.input.checked = true + } + else { + this.input.checked = false + this.isChecked = false + } + this.dispatch() + } + } + } + disconnectedCallback() { + this.removeEventListener('keyup', this.handleKeyup) + this.removeEventListener('change', this.handleChange) + } +}) + +//switch + +const smSwitch = document.createElement('template') +smSwitch.innerHTML = ` + +` + +customElements.define('sm-switch', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smSwitch.content.cloneNode(true)) + this.switch = this.shadowRoot.querySelector('.switch'); + this.input = this.shadowRoot.querySelector('input') + this.isChecked = false + this.isDisabled = false + } + + get disabled() { + return this.getAttribute('disabled') + } + + set disabled(val) { + if (val) { + this.disabled = true + this.setAttribute('disabled', '') + this.switch.classList.add('disabled') + } else { + this.disabled = false + this.removeAttribute('disabled') + this.switch.classList.remove('disabled') + + } + } + + get checked() { + return this.isChecked + } + + set checked(value) { + if (value) { + this.setAttribute('checked', '') + this.isChecked = true + this.input.checked = true + } else { + this.removeAttribute('checked') + this.isChecked = false + this.input.checked = false + } + } + + dispatch = () => { + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true + })) + } + + connectedCallback() { + if (this.hasAttribute('disabled')) + this.switch.classList.add('disabled') + if (this.hasAttribute('checked')) + this.input.checked = true + this.addEventListener('keyup', e => { + if ((e.code === "Enter" || e.code === "Space") && !this.isDisabled) { + this.input.click() + } + }) + this.input.addEventListener('click', e => { + if (this.input.checked) + this.checked = true + else + this.checked = false + this.dispatch() + }) + } +}) + +// select +const smSelect = document.createElement('template') +smSelect.innerHTML = ` + +
+
+
+ + + +
+
+ +
+
`; +customElements.define('sm-select', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smSelect.content.cloneNode(true)) + } + static get observedAttributes() { + return ['value'] + } + get value() { + return this.getAttribute('value') + } + set value(val) { + this.setAttribute('value', val) + } + + collapse() { + this.optionList.animate(this.slideUp, this.animationOptions) + this.optionList.classList.add('hide') + this.chevron.classList.remove('rotate') + this.open = false + } + connectedCallback() { + this.availableOptions + this.optionList = this.shadowRoot.querySelector('.options') + this.chevron = this.shadowRoot.querySelector('.toggle') + let slot = this.shadowRoot.querySelector('.options slot'), + selection = this.shadowRoot.querySelector('.selection'), + previousOption + this.open = false; + this.slideDown = [{ + transform: `translateY(-0.5rem)` + }, + { + transform: `translateY(0)` + } + ], + this.slideUp = [{ + transform: `translateY(0)` + }, + { + transform: `translateY(-0.5rem)` + } + ], + this.animationOptions = { + duration: 300, + fill: "forwards", + easing: 'ease' + } + selection.addEventListener('click', e => { + if (!this.open) { + this.optionList.classList.remove('hide') + this.optionList.animate(this.slideDown, this.animationOptions) + this.chevron.classList.add('rotate') + this.open = true + } else { + this.collapse() + } + }) + selection.addEventListener('keydown', e => { + if (e.code === 'ArrowDown' || e.code === 'ArrowRight') { + e.preventDefault() + this.availableOptions[0].focus() + } + if (e.code === 'Enter' || e.code === 'Space') + if (!this.open) { + this.optionList.classList.remove('hide') + this.optionList.animate(this.slideDown, this.animationOptions) + this.chevron.classList.add('rotate') + this.open = true + } else { + this.collapse() + } + }) + this.optionList.addEventListener('keydown', e => { + if (e.code === 'ArrowUp' || e.code === 'ArrowRight') { + e.preventDefault() + if (document.activeElement.previousElementSibling) { + document.activeElement.previousElementSibling.focus() + } + } + if (e.code === 'ArrowDown' || e.code === 'ArrowLeft') { + e.preventDefault() + if (document.activeElement.nextElementSibling) + document.activeElement.nextElementSibling.focus() + } + }) + this.addEventListener('optionSelected', e => { + if (previousOption !== e.target) { + this.setAttribute('value', e.detail.value) + this.shadowRoot.querySelector('.option-text').textContent = e.detail.text; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true, + detail: { + value: e.detail.value + } + })) + if (previousOption) { + previousOption.classList.remove('check-selected') + } + previousOption = e.target; + } + if (!e.detail.switching) + this.collapse() + + e.target.classList.add('check-selected') + }) + slot.addEventListener('slotchange', e => { + this.availableOptions = slot.assignedElements() + if (this.availableOptions[0]) { + let firstElement = this.availableOptions[0]; + previousOption = firstElement; + firstElement.classList.add('check-selected') + this.setAttribute('value', firstElement.getAttribute('value')) + this.shadowRoot.querySelector('.option-text').textContent = firstElement.textContent + this.availableOptions.forEach((element, index) => { + element.setAttribute('data-rank', index + 1); + element.setAttribute('tabindex', "0"); + }) + } + }); + document.addEventListener('mousedown', e => { + if (!this.contains(e.target) && this.open) { + this.collapse() + } + }) + } +}) + +// option +const smOption = document.createElement('template') +smOption.innerHTML = ` + +
+ + + + +
`; +customElements.define('sm-option', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smOption.content.cloneNode(true)) + } + + sendDetails(switching) { + let optionSelected = new CustomEvent('optionSelected', { + bubbles: true, + composed: true, + detail: { + text: this.textContent, + value: this.getAttribute('value'), + switching: switching + } + }) + this.dispatchEvent(optionSelected) + } + + connectedCallback() { + let validKey = [ + 'ArrowUp', + 'ArrowDown', + 'ArrowLeft', + 'ArrowRight' + ] + this.addEventListener('click', e => { + this.sendDetails() + }) + this.addEventListener('keyup', e => { + if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.sendDetails(false) + } + if (validKey.includes(e.code)) { + e.preventDefault() + this.sendDetails(true) + } + }) + if (this.hasAttribute('default')) { + setTimeout(() => { + this.sendDetails() + }, 0); + } + } +}) + +// select +const smStripSelect = document.createElement('template') +smStripSelect.innerHTML = ` + +
+
+ + Previous + + +
+ +
+ + Next + + +
+
`; +customElements.define('sm-strip-select', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smStripSelect.content.cloneNode(true)) + } + static get observedAttributes() { + return ['value'] + } + get value() { + return this.getAttribute('value') + } + set value(val) { + this.setAttribute('value', val) + } + scrollLeft = () => { + this.select.scrollBy({ + top: 0, + left: -this.scrollDistance, + behavior: 'smooth' + }) + } + + scrollRight = () => { + this.select.scrollBy({ + top: 0, + left: this.scrollDistance, + behavior: 'smooth' + }) + } + connectedCallback() { + let previousOption, + slot = this.shadowRoot.querySelector('slot'); + this.selectContainer = this.shadowRoot.querySelector('.select-container') + this.select = this.shadowRoot.querySelector('.select') + this.nextArrow = this.shadowRoot.querySelector('.next-item') + this.previousArrow = this.shadowRoot.querySelector('.previous-item') + this.nextGradient = this.shadowRoot.querySelector('.right') + this.previousGradient = this.shadowRoot.querySelector('.left') + this.selectOptions + this.scrollDistance = this.selectContainer.getBoundingClientRect().width + const firstElementObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + this.previousArrow.classList.add('hide') + this.previousGradient.classList.add('hide') + } else { + this.previousArrow.classList.remove('hide') + this.previousGradient.classList.remove('hide') + } + }, { + root: this.selectContainer, + threshold: 0.95 + }) + const lastElementObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + this.nextArrow.classList.add('hide') + this.nextGradient.classList.add('hide') + } else { + this.nextArrow.classList.remove('hide') + this.nextGradient.classList.remove('hide') + } + }, { + root: this.selectContainer, + threshold: 0.95 + }) + + const selectObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + this.scrollDistance = this.selectContainer.getBoundingClientRect().width + } + }) + + selectObserver.observe(this.selectContainer) + this.addEventListener('optionSelected', e => { + if (previousOption === e.target) return; + if (previousOption) + previousOption.classList.remove('active') + e.target.classList.add('active') + e.target.scrollIntoView({ + behavior: 'smooth', + inline: 'center', + block: 'nearest' + }) + this.setAttribute('value', e.detail.value) + this.dispatchEvent(new CustomEvent('change', { + bubbles: true, + composed: true + })) + previousOption = e.target; + }) + slot.addEventListener('slotchange', e => { + this.selectOptions = slot.assignedElements() + firstElementObserver.observe(this.selectOptions[0]) + lastElementObserver.observe(this.selectOptions[this.selectOptions.length - 1]) + if (this.selectOptions[0]) { + let firstElement = this.selectOptions[0]; + this.setAttribute('value', firstElement.getAttribute('value')) + firstElement.classList.add('active') + previousOption = firstElement; + } + }); + this.nextArrow.addEventListener('click', this.scrollRight) + this.previousArrow.addEventListener('click', this.scrollLeft) + } + + disconnectedCallback() { + this.nextArrow.removeEventListener('click', this.scrollRight) + this.previousArrow.removeEventListener('click', this.scrollLeft) + } +}) + +// option +const smStripOption = document.createElement('template') +smStripOption.innerHTML = ` + +
+ +
`; +customElements.define('sm-strip-option', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smStripOption.content.cloneNode(true)) + } + sendDetails() { + let optionSelected = new CustomEvent('optionSelected', { + bubbles: true, + composed: true, + detail: { + text: this.textContent, + value: this.getAttribute('value') + } + }) + this.dispatchEvent(optionSelected) + } + + connectedCallback() { + this.addEventListener('click', e => { + this.sendDetails() + }) + this.addEventListener('keyup', e => { + if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.sendDetails(false) + } + }) + if (this.hasAttribute('default')) { + setTimeout(() => { + this.sendDetails() + }, 0); + } + } +}) + +//popup +const smPopup = document.createElement('template') +smPopup.innerHTML = ` + + +`; +customElements.define('sm-popup', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smPopup.content.cloneNode(true)) + + this.allowClosing = false + } + + resumeScrolling = () => { + const scrollY = document.body.style.top; + window.scrollTo(0, parseInt(scrollY || '0') * -1); + setTimeout(() => { + document.body.style.overflow = 'auto'; + document.body.style.top= 'initial' + }, 300); + } + + show = (pinned, popupStack) => { + if (popupStack) + this.popupStack = popupStack + if (this.popupStack && !this.hasAttribute('open')) { + this.popupStack.push({ + popup: this, + permission: pinned + }) + if (this.popupStack.items.length > 1) { + this.popupStack.items[this.popupStack.items.length - 2].popup.classList.add('stacked') + } + this.dispatchEvent( + new CustomEvent("popupopened", { + bubbles: true, + detail: { + popup: this, + popupStack: this.popupStack + } + }) + ) + this.setAttribute('open', '') + this.pinned = pinned + } + this.popupContainer.classList.remove('hide') + this.popup.style.transform = 'none'; + document.body.style.overflow = 'hidden'; + document.body.style.top= `-${window.scrollY}px` + return this.popupStack + } + hide = () => { + if (window.innerWidth < 640) + this.popup.style.transform = 'translateY(100%)'; + else + this.popup.style.transform = 'translateY(3rem)'; + this.popupContainer.classList.add('hide') + this.removeAttribute('open') + if (typeof this.popupStack !== 'undefined') { + this.popupStack.pop() + if (this.popupStack.items.length) { + this.popupStack.items[this.popupStack.items.length - 1].popup.classList.remove('stacked') + } else { + this.resumeScrolling() + } + } else { + this.resumeScrolling() + } + + if (this.inputFields.length) { + setTimeout(() => { + this.inputFields.forEach(field => { + if (field.type === 'radio' || field.tagName === 'SM-CHECKBOX') + field.checked = false + if (field.tagName === 'SM-INPUT' || field.tagName === 'TEXTAREA'|| field.tagName === 'SM-TEXTAREA') + field.value = '' + }) + }, 300); + } + setTimeout(() => { + this.dispatchEvent( + new CustomEvent("popupclosed", { + bubbles: true, + detail: { + popup: this, + popupStack: this.popupStack + } + }) + ) + }, 300); + } + + handleTouchStart = (e) => { + this.touchStartY = e.changedTouches[0].clientY + this.popup.style.transition = 'transform 0.1s' + this.touchStartTime = e.timeStamp + } + + handleTouchMove = (e) => { + if (this.touchStartY < e.changedTouches[0].clientY) { + this.offset = e.changedTouches[0].clientY - this.touchStartY; + this.touchEndAnimataion = window.requestAnimationFrame(() => this.movePopup()) + } + /*else { + this.offset = this.touchStartY - e.changedTouches[0].clientY; + this.popup.style.transform = `translateY(-${this.offset}px)` + }*/ + } + + handleTouchEnd = (e) => { + this.touchEndTime = e.timeStamp + cancelAnimationFrame(this.touchEndAnimataion) + this.touchEndY = e.changedTouches[0].clientY + this.popup.style.transition = 'transform 0.3s' + this.threshold = this.popup.getBoundingClientRect().height * 0.3 + if (this.touchEndTime - this.touchStartTime > 200) { + if (this.touchEndY - this.touchStartY > this.threshold) { + if (this.pinned) { + this.show() + return + } else + this.hide() + } else { + this.show() + } + } else { + if (this.touchEndY > this.touchStartY) + if (this.pinned) { + this.show() + return + } + else + this.hide() + } + } + + movePopup = () => { + this.popup.style.transform = `translateY(${this.offset}px)` + } + + connectedCallback() { + this.pinned = false + this.popupStack + this.popupContainer = this.shadowRoot.querySelector('.popup-container') + this.popup = this.shadowRoot.querySelector('.popup') + this.popupBodySlot = this.shadowRoot.querySelector('.popup-body slot') + this.offset + this.popupHeader = this.shadowRoot.querySelector('.popup-top') + this.touchStartY = 0 + this.touchEndY = 0 + this.touchStartTime = 0 + this.touchEndTime = 0 + this.touchEndAnimataion; + this.threshold = this.popup.getBoundingClientRect().height * 0.3 + + if (this.hasAttribute('open')) + this.show() + this.popupContainer.addEventListener('mousedown', e => { + if (e.target === this.popupContainer && !this.pinned) { + if (this.pinned) { + this.show() + return + } else + this.hide() + } + }) + + this.popupBodySlot.addEventListener('slotchange', () => { + setTimeout(() => { + this.threshold = this.popup.getBoundingClientRect().height * 0.3 + }, 200); + this.inputFields = this.querySelectorAll('sm-input', 'sm-checkbox', 'textarea', 'sm-textarea', 'radio') + }) + + this.popupHeader.addEventListener('touchstart', (e) => { this.handleTouchStart(e) }, {passive: true}) + this.popupHeader.addEventListener('touchmove', (e) => {this.handleTouchMove(e)}, {passive: true}) + this.popupHeader.addEventListener('touchend', (e) => {this.handleTouchEnd(e)}, {passive: true}) + } + disconnectedCallback() { + this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, {passive: true}) + this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, {passive: true}) + this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, {passive: true}) + } +}) + +//carousel + +const smCarousel = document.createElement('template') +smCarousel.innerHTML = ` + + +`; + +customElements.define('sm-carousel', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smCarousel.content.cloneNode(true)) + } + + static get observedAttributes() { + return ['indicator'] + } + + scrollLeft = () => { + this.carousel.scrollBy({ + top: 0, + left: -this.scrollDistance, + behavior: 'smooth' + }) + } + + scrollRight = () => { + this.carousel.scrollBy({ + top: 0, + left: this.scrollDistance, + behavior: 'smooth' + }) + } + + connectedCallback() { + this.carousel = this.shadowRoot.querySelector('.carousel') + this.carouselContainer = this.shadowRoot.querySelector('.carousel-container') + this.carouselSlot = this.shadowRoot.querySelector('slot') + this.nextArrow = this.shadowRoot.querySelector('.next-item') + this.previousArrow = this.shadowRoot.querySelector('.previous-item') + this.indicatorsContainer = this.shadowRoot.querySelector('.indicators') + this.carouselItems + this.indicators + this.showIndicator = false + this.scrollDistance = this.carouselContainer.getBoundingClientRect().width / 3 + let frag = document.createDocumentFragment(); + if (this.hasAttribute('indicator')) + this.showIndicator = true + + + let firstVisible = false, + lastVisible = false + const allElementsObserver = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (this.showIndicator) + if (entry.isIntersecting) { + this.indicators[parseInt(entry.target.attributes.rank.textContent)].classList.add('active') + } + else + this.indicators[parseInt(entry.target.attributes.rank.textContent)].classList.remove('active') + if (!entry.target.previousElementSibling) + if (entry.isIntersecting) { + this.previousArrow.classList.remove('expand') + firstVisible = true + } + else { + this.previousArrow.classList.add('expand') + firstVisible = false + } + if (!entry.target.nextElementSibling) + if (entry.isIntersecting) { + this.nextArrow.classList.remove('expand') + lastVisible = true + } + else { + this.nextArrow.classList.add('expand') + + lastVisible = false + } + if (firstVisible && lastVisible) + this.indicatorsContainer.classList.add('hide') + else + this.indicatorsContainer.classList.remove('hide') + }) + }, { + root: this.carouselContainer, + threshold: 0.9 + }) + + const carouselObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + this.scrollDistance = this.carouselContainer.getBoundingClientRect().width / 3 + } + }) + + carouselObserver.observe(this.carouselContainer) + + this.carouselSlot.addEventListener('slotchange', e => { + this.carouselItems = this.carouselSlot.assignedElements() + this.carouselItems.forEach(item => allElementsObserver.observe(item)) + if (this.showIndicator) { + this.indicatorsContainer.innerHTML = `` + this.carouselItems.forEach((item, index) => { + let dot = document.createElement('div') + dot.classList.add('dot') + frag.append(dot) + item.setAttribute('rank', index) + }) + this.indicatorsContainer.append(frag) + this.indicators = this.indicatorsContainer.children + } + }) + + this.addEventListener('keyup', e => { + if (e.code === 'ArrowLeft') + this.scrollRight() + else + this.scrollRight() + }) + + this.nextArrow.addEventListener('click', this.scrollRight) + this.previousArrow.addEventListener('click', this.scrollLeft) + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'indicator') { + if (this.hasAttribute('indicator')) + this.showIndicator = true + else + this.showIndicator = false + } + } + + disconnectedCallback() { + this.nextArrow.removeEventListener('click', this.scrollRight) + this.previousArrow.removeEventListener('click', this.scrollLeft) + } +}) + +//notifications + +const smNotifications = document.createElement('template') +smNotifications.innerHTML = ` + +
+
+` + +customElements.define('sm-notifications', class extends HTMLElement { + constructor() { + super() + this.shadow = this.attachShadow({ + mode: 'open' + }).append(smNotifications.content.cloneNode(true)) + } + + handleTouchStart = (e) => { + this.notification = e.target.closest('.notification') + this.touchStartX = e.changedTouches[0].clientX + this.notification.style.transition = 'initial' + this.touchStartTime = e.timeStamp + } + + handleTouchMove = (e) => { + e.preventDefault() + if (this.touchStartX < e.changedTouches[0].clientX) { + this.offset = e.changedTouches[0].clientX - this.touchStartX; + this.touchEndAnimataion = requestAnimationFrame(this.movePopup) + } else { + this.offset = -(this.touchStartX - e.changedTouches[0].clientX); + this.touchEndAnimataion = requestAnimationFrame(this.movePopup) + } + } + + handleTouchEnd = (e) => { + this.notification.style.transition = 'transform 0.3s, opacity 0.3s' + this.touchEndTime = e.timeStamp + cancelAnimationFrame(this.touchEndAnimataion) + this.touchEndX = e.changedTouches[0].clientX + if (this.touchEndTime - this.touchStartTime > 200) { + if (this.touchEndX - this.touchStartX > this.threshold) { + this.removeNotification(this.notification) + } else if (this.touchStartX - this.touchEndX > this.threshold) { + this.removeNotification(this.notification, true) + } else { + this.resetPosition() + } + } else { + if (this.touchEndX > this.touchStartX) { + this.removeNotification(this.notification) + } else { + this.removeNotification(this.notification, true) + } + } + } + + movePopup = () => { + this.notification.style.transform = `translateX(${this.offset}px)` + } + + resetPosition = () => { + this.notification.style.transform = `translateX(0)` + } + + push = (messageBody, type, pinned) => { + let notification = document.createElement('div'), + composition = `` + notification.classList.add('notification') + if (pinned) + notification.classList.add('pinned') + if (type === 'error') { + composition += ` + + + + + ` + } else if (type === 'success') { + composition += ` + + + ` + } + composition += ` +

${messageBody}

+ + Close + + + ` + notification.innerHTML = composition + this.notificationPanel.prepend(notification) + if (window.innerWidth > 640) { + notification.animate([{ + transform: `translateX(1rem)`, + opacity: '0' + }, + { + transform: 'translateX(0)', + opacity: '1' + } + ], this.animationOptions).onfinish = () => { + notification.setAttribute('style', `transform: none;`); + } + } else { + notification.setAttribute('style', `transform: translateY(0); opacity: 1`) + } + notification.addEventListener('touchstart', this.handleTouchStart) + notification.addEventListener('touchmove', this.handleTouchMove) + notification.addEventListener('touchend', this.handleTouchEnd) + } + + removeNotification = (notification, toLeft) => { + if (!this.offset) + this.offset = 0; + + if (toLeft) + notification.animate([{ + transform: `translateX(${this.offset}px)`, + opacity: '1' + }, + { + transform: `translateX(-100%)`, + opacity: '0' + } + ], this.animationOptions).onfinish = () => { + notification.remove() + } + else { + notification.animate([{ + transform: `translateX(${this.offset}px)`, + opacity: '1' + }, + { + transform: `translateX(100%)`, + opacity: '0' + } + ], this.animationOptions).onfinish = () => { + notification.remove() + } + } + } + + clearAll = () => { + Array.from(this.notificationPanel.children).forEach(child => { + this.removeNotification(child) + }) + } + + connectedCallback() { + this.notificationPanel = this.shadowRoot.querySelector('.notification-panel') + this.animationOptions = { + duration: 300, + fill: "forwards", + easing: "ease" + } + this.fontSize = Number(window.getComputedStyle(document.body).getPropertyValue('font-size').match(/\d+/)[0]) + this.notification + this.offset + this.touchStartX = 0 + this.touchEndX = 0 + this.touchStartTime = 0 + this.touchEndTime = 0 + this.threshold = this.notificationPanel.getBoundingClientRect().width * 0.3 + this.touchEndAnimataion; + + this.notificationPanel.addEventListener('click', e => { + if (e.target.closest('.close'))( + this.removeNotification(e.target.closest('.notification')) + ) + }) + + const observer = new MutationObserver(mutationList => { + mutationList.forEach(mutation => { + if (mutation.type === 'childList') { + if (mutation.addedNodes.length) { + if (!mutation.addedNodes[0].classList.contains('pinned')) + setTimeout(() => { + this.removeNotification(mutation.addedNodes[0]) + }, 5000); + if (window.innerWidth > 640) + this.notificationPanel.style.padding = '1.5rem 0 3rem 1.5rem'; + else + this.notificationPanel.style.padding = '1rem 1rem 2rem 1rem'; + } else if (mutation.removedNodes.length && !this.notificationPanel.children.length) { + this.notificationPanel.style.padding = 0; + } + } + }) + }) + observer.observe(this.notificationPanel, { + attributes: true, + childList: true, + subtree: true + }) + } +}) + + + +// sm-menu +const smMenu = document.createElement('template') +smMenu.innerHTML = ` + +
+ +
+ +
+
`; +customElements.define('sm-menu', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smMenu.content.cloneNode(true)) + } + static get observedAttributes() { + return ['value'] + } + get value() { + return this.getAttribute('value') + } + set value(val) { + this.setAttribute('value', val) + } + expand = () => { + if (!this.open) { + this.optionList.classList.remove('hide') + this.optionList.classList.add('no-transformations') + this.open = true + this.icon.classList.add('focused') + this.availableOptions.forEach(option => { + option.setAttribute('tabindex', '0') + }) + } + } + collapse() { + if (this.open) { + this.open = false + this.icon.classList.remove('focused') + this.optionList.classList.add('hide') + this.optionList.classList.remove('no-transformations') + this.availableOptions.forEach(option => { + option.removeAttribute('tabindex') + }) + } + } + connectedCallback() { + this.availableOptions + this.containerDimensions + this.optionList = this.shadowRoot.querySelector('.options') + let slot = this.shadowRoot.querySelector('.options slot'), + menu = this.shadowRoot.querySelector('.menu') + this.icon = this.shadowRoot.querySelector('.icon') + this.open = false; + menu.addEventListener('click', e => { + if (!this.open) { + this.expand() + } else { + this.collapse() + } + }) + menu.addEventListener('keydown', e => { + if (e.code === 'ArrowDown' || e.code === 'ArrowRight') { + e.preventDefault() + this.availableOptions[0].focus() + } + if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + if (!this.open) { + this.expand() + } else { + this.collapse() + } + } + }) + this.optionList.addEventListener('keydown', e => { + if (e.code === 'ArrowUp' || e.code === 'ArrowRight') { + e.preventDefault() + if (document.activeElement.previousElementSibling) { + document.activeElement.previousElementSibling.focus() + } + } + if (e.code === 'ArrowDown' || e.code === 'ArrowLeft') { + e.preventDefault() + if (document.activeElement.nextElementSibling) + document.activeElement.nextElementSibling.focus() + } + }) + this.optionList.addEventListener('click', e => { + this.collapse() + }) + slot.addEventListener('slotchange', e => { + this.availableOptions = slot.assignedElements() + this.containerDimensions = this.optionList.getBoundingClientRect() + }); + window.addEventListener('mousedown', e => { + if (!this.contains(e.target) && e.button !== 2) { + this.collapse() + } + }) + } +}) + +// option +const smMenuOption = document.createElement('template') +smMenuOption.innerHTML = ` + +
+ +
`; +customElements.define('sm-menu-option', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smMenuOption.content.cloneNode(true)) + } + + connectedCallback() { + this.addEventListener('keyup', e => { + if (e.code === 'Enter' || e.code === 'Space') { + e.preventDefault() + this.click() + } + }) + } +}) + +// tab-header + +const smTabHeader = document.createElement('template') +smTabHeader.innerHTML = ` + +
+
+ +
+
+
+`; + +customElements.define('sm-tab-header', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smTabHeader.content.cloneNode(true)) + + this.indicator = this.shadowRoot.querySelector('.indicator'); + this.tabSlot = this.shadowRoot.querySelector('slot'); + this.tabHeader = this.shadowRoot.querySelector('.tab-header'); + } + + sendDetails(element) { + this.dispatchEvent( + new CustomEvent("switchtab", { + bubbles: true, + detail: { + target: this.target, + rank: parseInt(element.getAttribute('rank')) + } + }) + ) + } + + moveIndiactor(tabDimensions) { + //if(this.isTab) + this.indicator.setAttribute('style', `width: ${tabDimensions.width}px; transform: translateX(${tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px)`) + //else + //this.indicator.setAttribute('style', `width: calc(${tabDimensions.width}px - 1.6rem); transform: translateX(calc(${ tabDimensions.left - this.tabHeader.getBoundingClientRect().left + this.tabHeader.scrollLeft}px + 0.8rem)`) + } + + connectedCallback() { + if (!this.hasAttribute('target') || this.getAttribute('target').value === '') return; + this.prevTab + this.allTabs + this.activeTab + this.isTab = false + this.target = this.getAttribute('target') + + if (this.hasAttribute('variant') && this.getAttribute('variant') === 'tab') { + this.isTab = true + } + + this.tabSlot.addEventListener('slotchange', () => { + this.tabSlot.assignedElements().forEach((tab, index) => { + tab.setAttribute('rank', index) + }) + }) + this.allTabs = this.tabSlot.assignedElements(); + + this.tabSlot.addEventListener('click', e => { + if (e.target === this.prevTab || !e.target.closest('sm-tab')) + return + if (this.prevTab) + this.prevTab.classList.remove('active') + e.target.classList.add('active') + + e.target.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center' + }) + this.moveIndiactor(e.target.getBoundingClientRect()) + this.sendDetails(e.target) + this.prevTab = e.target; + this.activeTab = e.target; + }) + let resizeObserver = new ResizeObserver(entries => { + entries.forEach((entry) => { + if (this.prevTab) { + let tabDimensions = this.activeTab.getBoundingClientRect(); + this.moveIndiactor(tabDimensions) + } + }) + }) + resizeObserver.observe(this) + let observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + this.indicator.style.transition = 'none' + if (this.activeTab) { + let tabDimensions = this.activeTab.getBoundingClientRect(); + this.moveIndiactor(tabDimensions) + } else { + this.allTabs[0].classList.add('active') + let tabDimensions = this.allTabs[0].getBoundingClientRect(); + this.moveIndiactor(tabDimensions) + this.sendDetails(this.allTabs[0]) + this.prevTab = this.tabSlot.assignedElements()[0]; + this.activeTab = this.prevTab; + } + } + }) + }, { + threshold: 1.0 + }) + observer.observe(this) + } +}) + +// tab-panels + +const smTabPanels = document.createElement('template') +smTabPanels.innerHTML = ` + +
+ Nothing to see here. +
+`; + +customElements.define('sm-tab-panels', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(smTabPanels.content.cloneNode(true)) + this.panelSlot = this.shadowRoot.querySelector('slot'); + } + connectedCallback() { + + //animations + let flyInLeft = [{ + opacity: 0, + transform: 'translateX(-1rem)' + }, + { + opacity: 1, + transform: 'none' + } + ], + flyInRight = [{ + opacity: 0, + transform: 'translateX(1rem)' + }, + { + opacity: 1, + transform: 'none' + } + ], + flyOutLeft = [{ + opacity: 1, + transform: 'none' + }, + { + opacity: 0, + transform: 'translateX(-1rem)' + } + ], + flyOutRight = [{ + opacity: 1, + transform: 'none' + }, + { + opacity: 0, + transform: 'translateX(1rem)' + } + ], + animationOptions = { + duration: 300, + fill: 'forwards', + easing: 'ease' + } + this.prevPanel + this.allPanels + this.previousRank + + this.panelSlot.addEventListener('slotchange', () => { + this.panelSlot.assignedElements().forEach((panel) => { + panel.classList.add('hide-completely') + }) + }) + this.allPanels = this.panelSlot.assignedElements() + this._targetBodyFlyRight = (targetBody) => { + targetBody.classList.remove('hide-completely') + targetBody.animate(flyInRight, animationOptions) + } + this._targetBodyFlyLeft = (targetBody) => { + targetBody.classList.remove('hide-completely') + targetBody.animate(flyInLeft, animationOptions) + } + document.addEventListener('switchtab', e => { + if (e.detail.target !== this.id) + return + + if (this.prevPanel) { + let targetBody = this.allPanels[e.detail.rank], + currentBody = this.prevPanel; + if (this.previousRank < e.detail.rank) { + if (currentBody && !targetBody) + currentBody.animate(flyOutLeft, animationOptions).onfinish = () => { + currentBody.classList.add('hide-completely') + } + else if (targetBody && !currentBody) { + this._targetBodyFlyRight(targetBody) + } else if (currentBody && targetBody) { + currentBody.animate(flyOutLeft, animationOptions).onfinish = () => { + currentBody.classList.add('hide-completely') + this._targetBodyFlyRight(targetBody) + } + } + } else { + if (currentBody && !targetBody) + currentBody.animate(flyOutRight, animationOptions).onfinish = () => { + currentBody.classList.add('hide-completely') + } + else if (targetBody && !currentBody) { + this._targetBodyFlyLeft(targetBody) + } else if (currentBody && targetBody) { + currentBody.animate(flyOutRight, animationOptions).onfinish = () => { + currentBody.classList.add('hide-completely') + this._targetBodyFlyLeft(targetBody) + } + } + } + } else { + this.allPanels[e.detail.rank].classList.remove('hide-completely') + } + this.previousRank = e.detail.rank + this.prevPanel = this.allPanels[e.detail.rank]; + }) + } +}) + + +const slidingSection = document.createElement('template') +slidingSection.innerHTML = ` + +
+ +
+` + +customElements.define('sm-sliding-section', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(slidingSection.content.cloneNode(true)) + } + connectedCallback() { + + } +}) + +const section = document.createElement('template') +section.innerHTML = ` + +
+ +
+` + +customElements.define('sm-section', class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(section.content.cloneNode(true)) + } +}) + +const textField = document.createElement('template') +textField.innerHTML = ` + +
+
+
+ + Edit + + + + Save + + +
+
+` + +customElements.define('text-field', class extends HTMLElement{ + constructor(){ + super() + this.attachShadow({ + mode: 'open' + }).append(textField.content.cloneNode(true)) + + this.textField = this.shadowRoot.querySelector('.text-field') + this.textContainer = this.textField.children[0] + this.iconsContainer = this.textField.children[1] + this.editButton = this.textField.querySelector('.edit-button') + this.saveButton = this.textField.querySelector('.save-button') + this.isTextEditable = false + this.isDisabled = false + } + + static get observedAttributes(){ + return ['disable'] + } + + get value(){ + return this.text + } + set value(val) { + this.text = val + this.textContainer.textContent = val + this.setAttribute('value', val) + } + set disabled(val) { + this.isDisabled = val + if(this.isDisabled) + this.setAttribute('disable', '') + else + this.removeAttribute('disable') + } + fireEvent = (value) => { + let event = new CustomEvent('contentchanged', { + bubbles: true, + cancelable: true, + composed: true, + detail: { + value + } + }); + this.dispatchEvent(event); + } + + setEditable = () => { + if(this.isTextEditable) return + this.textContainer.contentEditable = true + this.textContainer.classList.add('editable') + this.textContainer.focus() + document.execCommand('selectAll', false, null); + this.editButton.animate(this.rotateOut, this.animOptions).onfinish = () => { + this.editButton.classList.add('hide') + } + setTimeout(() => { + this.saveButton.classList.remove('hide') + this.saveButton.animate(this.rotateIn, this.animOptions) + }, 100); + this.isTextEditable = true + } + setNonEditable = () => { + if (!this.isTextEditable) return + this.textContainer.contentEditable = false + this.textContainer.classList.remove('editable') + + if (this.text !== this.textContainer.textContent.trim()) { + this.setAttribute('value', this.textContainer.textContent) + this.text = this.textContainer.textContent.trim() + this.fireEvent(this.text) + } + this.saveButton.animate(this.rotateOut, this.animOptions).onfinish = () => { + this.saveButton.classList.add('hide') + } + setTimeout(() => { + this.editButton.classList.remove('hide') + this.editButton.animate(this.rotateIn, this.animOptions) + }, 100); + this.isTextEditable = false + } + + revert = () => { + if (this.textContainer.isContentEditable) { + this.value = this.text + this.setNonEditable() + } + } + + connectedCallback(){ + this.text + if (this.hasAttribute('value')) { + this.text = this.getAttribute('value') + this.textContainer.textContent = this.text + } + if(this.hasAttribute('disable')) + this.isDisabled = true + else + this.isDisabled = false + + this.rotateOut = [ + { + transform: 'rotate(0)', + opacity: 1 + }, + { + transform: 'rotate(90deg)', + opacity: 0 + }, + ] + this.rotateIn = [ + { + transform: 'rotate(-90deg)', + opacity: 0 + }, + { + transform: 'rotate(0)', + opacity: 1 + }, + ] + this.animOptions = { + duration: 300, + easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', + fill: 'forwards' + } + if (!this.isDisabled) { + this.iconsContainer.classList.remove('hide') + this.textContainer.addEventListener('dblclick', this.setEditable) + this.editButton.addEventListener('click', this.setEditable) + this.saveButton.addEventListener('click', this.setNonEditable) + } + } + attributeChangedCallback(name) { + if (name === 'disable') { + if (this.hasAttribute('disable')) { + this.iconsContainer.classList.add('hide') + this.textContainer.removeEventListener('dblclick', this.setEditable) + this.editButton.removeEventListener('click', this.setEditable) + this.saveButton.removeEventListener('click', this.setNonEditable) + this.revert() + } + else { + this.iconsContainer.classList.remove('hide') + this.textContainer.addEventListener('dblclick', this.setEditable) + this.editButton.addEventListener('click', this.setEditable) + this.saveButton.addEventListener('click', this.setNonEditable) + } + } + } + disconnectedCallback() { + this.textContainer.removeEventListener('dblclick', this.setEditable) + this.editButton.removeEventListener('click', this.setEditable) + this.saveButton.removeEventListener('click', this.setNonEditable) + } +}) + +//Color Grid +const colorGrid = document.createElement('template'); +colorGrid.innerHTML =` + +
+
`; + +customElements.define('color-grid', +class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(colorGrid.content.cloneNode(true)) + + this.colorArray = [] + this.container = this.shadowRoot.querySelector('.color-tile-container') + } + + set colors(arr) { + this.colorArray = arr + this.renderTiles() + } + + set selectedColor(color) { + if (this.colorArray.includes(color) && this.container.querySelector(`[data-color="${color}"]`)) { + this.container.querySelector(`[data-color="${color}"] input`).checked = true + } + } + + randString(length) { + let result = ''; + let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + for (let i = 0; i < length; i++) + result += characters.charAt(Math.floor(Math.random() * characters.length)); + return result; + } + + renderTiles() { + this.container.innerHTML = '' + const frag = document.createDocumentFragment() + const groupName = this.randString(6) + this.colorArray.forEach(color => { + const label = document.createElement('label') + label.classList.add('color-tile') + label.setAttribute('data-color', color) + if(color.includes('--')) + label.setAttribute('style', `background-color: var(${color})`) + else + label.setAttribute('style', `background-color: ${color}`) + label.innerHTML = ` + +
+ ` + frag.append(label) + }) + this.container.append(frag) + } + + handleChange(e) { + const clickedTile = e.target.closest('.color-tile') + const clickedTileColor = clickedTile.dataset.color + const tileSelected = new CustomEvent('colorselected', { + bubbles: true, + composed: true, + detail: { + value: clickedTileColor, + } + }) + this.dispatchEvent(tileSelected) + } + + connectedCallback() { + this.container.addEventListener('change', this.handleChange) + } + + disconnectedCallback() { + this.container.removeEventListener('change', this.handleChange) + } +}) + +const pinInput = document.createElement('template'); +pinInput.innerHTML = ` + + +
+
+ +
+`; + +customElements.define('pin-input', + + class extends HTMLElement { + constructor() { + super() + this.attachShadow({ + mode: 'open' + }).append(pinInput.content.cloneNode(true)) + + this.pinDigits = 4 + + this.arrayOfInput = []; + this.container = this.shadowRoot.querySelector('.pin-container'); + this.toggleButton = this.shadowRoot.querySelector('button') + } + + set value(val) { + this.arrayOfInput.forEach((input, index) => input.value = val[index] ? val[index] : '') + } + + get value() { + return this.getValue() + } + + set pinLength(val) { + this.pinDigits = val + this.setAttribute('pin-length', val) + this.style.setProperty('--pin-length', val) + this.render() + } + + get isValid(){ + return this.arrayOfInput.every(input => input.value.trim().length) + } + + clear = () => { + this.value = '' + } + + focusIn = () => { + this.arrayOfInput[0].focus(); + } + + getValue = () => { + return this.arrayOfInput.reduce((acc, val) => { + return acc += val.value + }, '') + } + + render = () => { + this.container.innerHTML = '' + const frag = document.createDocumentFragment(); + + for (let i = 0; i < this.pinDigits; i++) { + const inputBox = document.createElement('input') + inputBox.setAttribute('type', 'password') + inputBox.setAttribute('inputmode', 'numeric') + inputBox.setAttribute('maxlength', '1') + inputBox.setAttribute('required', '') + this.arrayOfInput.push(inputBox); + frag.append(inputBox); + } + this.container.append(frag); + } + + handleKeydown = (e) => { + const activeInput = e.target.closest('input') + if (/[0-9]/.test(e.key)) { + if (activeInput.value.trim().length > 2) { + e.preventDefault(); + } + else { + if (activeInput.nextElementSibling) { + setTimeout(() => { + activeInput.nextElementSibling.focus(); + }, 0) + } + } + } + else if (e.key === "Backspace") { + if(activeInput.previousElementSibling) + setTimeout(() => { + activeInput.previousElementSibling.focus(); + }, 0) + } + else if (e.key.length === 1 && !/[0-9]/.test(e.key)) { + e.preventDefault(); + } + } + + handleInput = () => { + if (this.isValid) { + this.fireEvent(this.getValue()) + } + } + + fireEvent = (value) => { + let event = new CustomEvent('pincomplete', { + bubbles: true, + cancelable: true, + composed: true, + detail: { + value + } + }); + this.dispatchEvent(event); + } + + toggleVisiblity = () => { + if (this.arrayOfInput[0].getAttribute('type') === 'password') { + this.toggleButton.innerHTML = ` + + Hide + ` + this.arrayOfInput.forEach(input => input.setAttribute('type', 'text')) + } + else { + this.toggleButton.innerHTML = ` + + Show + ` + this.arrayOfInput.forEach(input => input.setAttribute('type', 'password')) + + } + } + + connectedCallback() { + if (this.hasAttribute('pin-length')) { + const pinLength = parseInt(this.getAttribute('pin-length')) + this.pinDigits = pinLength + this.style.setProperty('--pin-length', pinLength) + } + + this.render() + + this.toggleButton.addEventListener('click', this.toggleVisiblity) + + this.container.addEventListener('input', this.handleInput); + this.container.addEventListener('keydown', this.handleKeydown); + } + disconnectedCallback() { + this.toggleButton.removeEventListener('click', this.toggleVisiblity) + + this.container.removeEventListener('input', this.handleInput); + this.container.removeEventListener('keydown', this.handleKeydown); + } + })