mirror of
https://github.com/FJurmanovic/wallet-web.git
synced 2026-02-06 06:08:10 +00:00
custom dropdown element
This commit is contained in:
@@ -48,10 +48,10 @@ class BaseElement extends HTMLElement {
|
||||
return this.appMain?.isAuth();
|
||||
}
|
||||
|
||||
public bindEvents = (): void => {
|
||||
const _elems = this.querySelectorAll("[data-action]");
|
||||
public bindEvents = (attrName): void => {
|
||||
const _elems = this.querySelectorAll(`[${attrName}]`);
|
||||
_elems?.forEach((el) => {
|
||||
for (const action of (el.getAttribute("data-action") || "")
|
||||
for (const action of (el.getAttribute(attrName) || "")
|
||||
.trim()
|
||||
.split(/\s+/)) {
|
||||
const eventSep = action.lastIndexOf(":");
|
||||
@@ -88,7 +88,8 @@ class BaseElement extends HTMLElement {
|
||||
|
||||
update = (): void => {
|
||||
render(this.render(), this);
|
||||
this.bindEvents();
|
||||
this.bindEvents("data-action");
|
||||
this.bindEvents("app-action");
|
||||
this.updateCallback();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { attr, controller, target } from "@github/catalyst";
|
||||
import { findMethod, firstUpper } from "core/utils";
|
||||
import { html, TemplateResult } from "@github/jtml";
|
||||
import { RouterService } from "core/services";
|
||||
import { html } from "@github/jtml";
|
||||
import randomId from "core/utils/random-id";
|
||||
import validator from "validator";
|
||||
import { validatorErrors } from "core/constants";
|
||||
@@ -14,12 +13,20 @@ class AppDropdownElement extends BaseComponentElement {
|
||||
@attr rules: string;
|
||||
@target main: HTMLElement;
|
||||
@target inp: HTMLElement;
|
||||
@attr displaykey: string;
|
||||
@attr valuekey: string;
|
||||
@attr displaykey: string = "name";
|
||||
@attr valuekey: string = "id";
|
||||
@attr fetch: string;
|
||||
fetchFunc: any;
|
||||
error: string;
|
||||
|
||||
error: boolean;
|
||||
errorMessage: string;
|
||||
|
||||
searchPhrase: string;
|
||||
|
||||
randId: string;
|
||||
value: string;
|
||||
|
||||
@attr isOpen: boolean = false;
|
||||
|
||||
items: Array<any>;
|
||||
totalItems: number;
|
||||
@@ -51,32 +58,20 @@ class AppDropdownElement extends BaseComponentElement {
|
||||
items = [];
|
||||
}
|
||||
this.items = items;
|
||||
this.renderOptions(items);
|
||||
this.renderOptions(items);
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
private renderOptions = (items) => {
|
||||
const displayKey = this.displaykey || "name";
|
||||
const valueKey = this.valuekey || "id";
|
||||
|
||||
const options = items?.map((item) => {
|
||||
const val = { name: item[displayKey], value: item[valueKey] };
|
||||
if (
|
||||
this.optionValues.some((value) => {
|
||||
if (value.name == val.name && value.value == val.value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
)
|
||||
return;
|
||||
const _option = document.createElement("option");
|
||||
_option.setAttribute("value", val.value);
|
||||
_option.innerText = val.name;
|
||||
this.inp?.appendChild(_option);
|
||||
get selectedItem() {
|
||||
const { value, valuekey, items } = this;
|
||||
const item = items?.find((item) => {
|
||||
return value == item[valuekey];
|
||||
});
|
||||
};
|
||||
|
||||
console.log(item, value, valuekey);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
get optionValues() {
|
||||
let values = [];
|
||||
@@ -88,10 +83,6 @@ class AppDropdownElement extends BaseComponentElement {
|
||||
return values;
|
||||
}
|
||||
|
||||
onChange = () => {
|
||||
this.renderOptions(this.items);
|
||||
};
|
||||
|
||||
public elementConnected = (): void => {
|
||||
this.randId = `${name}${randomId()}`;
|
||||
this.fetchFunc = findMethod(this.fetch, this.appMain);
|
||||
@@ -104,6 +95,10 @@ class AppDropdownElement extends BaseComponentElement {
|
||||
this.getItems(options);
|
||||
};
|
||||
|
||||
attributeChangedCallback(): void {
|
||||
this.update();
|
||||
}
|
||||
|
||||
get valid(): boolean {
|
||||
return !!this.error;
|
||||
}
|
||||
@@ -144,13 +139,102 @@ class AppDropdownElement extends BaseComponentElement {
|
||||
return _return;
|
||||
}
|
||||
|
||||
render = (): TemplateResult => {
|
||||
return html`<div class="input-main" data-target="app-dropdown.main">
|
||||
<select
|
||||
data-target="app-dropdown.inp"
|
||||
data-action="change:app-dropdown#onChange"
|
||||
></select>
|
||||
</div>`;
|
||||
openDropdown = () => {
|
||||
this.isOpen = true;
|
||||
};
|
||||
|
||||
stopPropagation = (e) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
toggleDropdown = () => {
|
||||
const isOpen = this.isOpen;
|
||||
this.isOpen = !isOpen;
|
||||
};
|
||||
|
||||
itemSelected = (e) => {
|
||||
const value = (e.target as HTMLSpanElement).getAttribute("data-value");
|
||||
this.value = value;
|
||||
this.isOpen = false;
|
||||
};
|
||||
|
||||
get _value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const {
|
||||
label,
|
||||
error,
|
||||
errorMessage,
|
||||
isOpen,
|
||||
searchPhrase,
|
||||
items,
|
||||
selectedItem,
|
||||
displaykey,
|
||||
valuekey,
|
||||
} = this;
|
||||
|
||||
console.log(isOpen);
|
||||
|
||||
const renderItem = (item) => {
|
||||
return html` <li
|
||||
class="dropdown-custom-listitem ${selectedItem?.[valuekey] ==
|
||||
item[valuekey]
|
||||
? "--selected"
|
||||
: ""}"
|
||||
app-action="click:app-dropdown#itemSelected"
|
||||
data-value="${item[valuekey]}"
|
||||
>
|
||||
${item[displaykey]}
|
||||
</li>`;
|
||||
};
|
||||
|
||||
const renderItems = (_items) => {
|
||||
return _items.map((item) => renderItem(item));
|
||||
};
|
||||
|
||||
return html`
|
||||
<div>
|
||||
<label app-action="click:app-dropdown#openDropdown">
|
||||
${label ? html`<div>${label}</div>` : html``}
|
||||
<div
|
||||
class="dropdown-custom"
|
||||
app-action="click:app-dropdown#stopPropagation"
|
||||
>
|
||||
<div
|
||||
class="dropdown-custom-top"
|
||||
app-action="click:app-dropdown#toggleDropdown"
|
||||
>
|
||||
<span class="dropdown-custom-fieldname"
|
||||
>${selectedItem
|
||||
? selectedItem[displaykey]
|
||||
: "Select"}</span
|
||||
>
|
||||
</div>
|
||||
${isOpen
|
||||
? html`
|
||||
<div class="dropdown-custom-open">
|
||||
<input
|
||||
class="dropdown-custom-search"
|
||||
type="text"
|
||||
value="${searchPhrase}"
|
||||
app-action="input:app-dropdown#phraseChange"
|
||||
autofocus
|
||||
/>
|
||||
<ul class="dropdown-custom-list">
|
||||
${renderItems(items)}
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
${error
|
||||
? html` <div class="h5 text-red">${errorMessage}</div>`
|
||||
: html``}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -65,13 +65,11 @@ class AppFormElement extends BaseComponentElement {
|
||||
get values(): any {
|
||||
const formObject: any = {};
|
||||
this.inputField.forEach((input: InputFieldElement) => {
|
||||
const inputType = input.inp;
|
||||
formObject[input.name] = (inputType as HTMLInputElement).value;
|
||||
formObject[input.name] = input._value;
|
||||
});
|
||||
this.appDropdown?.forEach((input: AppDropdownElement) => {
|
||||
if (input.required && (input.inp as HTMLSelectElement).value) {
|
||||
const inputType = input.inp;
|
||||
formObject[input.name] = (inputType as HTMLSelectElement).value;
|
||||
if (input.required && input.value) {
|
||||
formObject[input.name] = input._value;
|
||||
}
|
||||
});
|
||||
return formObject;
|
||||
@@ -114,7 +112,7 @@ class AppFormElement extends BaseComponentElement {
|
||||
if (hasCancel) {
|
||||
return html`<button
|
||||
type="button"
|
||||
data-action="click:app-form#goBack"
|
||||
app-action="click:app-form#goBack"
|
||||
>
|
||||
Cancel
|
||||
</button>`;
|
||||
@@ -123,7 +121,7 @@ class AppFormElement extends BaseComponentElement {
|
||||
};
|
||||
|
||||
return html`<form
|
||||
data-action="submit:app-form#onSubmit"
|
||||
app-action="submit:app-form#onSubmit"
|
||||
data-target="app-form.formElement"
|
||||
>
|
||||
<slot data-target="app-form.innerSlot"></slot>
|
||||
|
||||
@@ -61,7 +61,7 @@ class AppLinkElement extends BaseComponentElement {
|
||||
: html`<a
|
||||
class="btn btn-link"
|
||||
data-target="app-link.main"
|
||||
data-action="click:app-link#goTo"
|
||||
app-action="click:app-link#goTo"
|
||||
href="${this.to}"
|
||||
style="text-decoration: underline; cursor: pointer;"
|
||||
>${this.title}</a
|
||||
|
||||
@@ -123,7 +123,7 @@ class AppPaginationElement extends BaseComponentElement {
|
||||
class="btn btn-primary btn-squared ${page <= 1
|
||||
? "disabled"
|
||||
: ""}"
|
||||
data-action="click:app-pagination#pageBack"
|
||||
app-action="click:app-pagination#pageBack"
|
||||
>
|
||||
Prev
|
||||
</button>
|
||||
@@ -132,7 +132,7 @@ class AppPaginationElement extends BaseComponentElement {
|
||||
pageRange
|
||||
? "disabled"
|
||||
: ""}"
|
||||
data-action="click:app-pagination#pageNext"
|
||||
app-action="click:app-pagination#pageNext"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
|
||||
@@ -38,6 +38,10 @@ class InputFieldElement extends BaseComponentElement {
|
||||
return this.rules.includes("required");
|
||||
}
|
||||
|
||||
get _value() {
|
||||
return (this.inp as HTMLInputElement)?.value;
|
||||
}
|
||||
|
||||
validate = (): boolean => {
|
||||
let _return = true;
|
||||
const rules = this.rules?.split("|").filter((a) => a);
|
||||
@@ -104,7 +108,7 @@ class InputFieldElement extends BaseComponentElement {
|
||||
return html` <input
|
||||
type="${this.type}"
|
||||
data-target="input-field.inp"
|
||||
data-action="
|
||||
app-action="
|
||||
input:input-field#inputChange
|
||||
blur:input-field#validateDisplay
|
||||
"
|
||||
|
||||
@@ -42,7 +42,7 @@ class MenuItemElement extends BaseComponentElement {
|
||||
${this.customaction
|
||||
? html`<div
|
||||
data-target="menu-item.customButton"
|
||||
data-action="${this.customaction}"
|
||||
app-action="${this.customaction}"
|
||||
>
|
||||
+
|
||||
</div>`
|
||||
|
||||
@@ -53,7 +53,7 @@ class HomePageElement extends BasePageElement {
|
||||
|
||||
render = (): TemplateResult => {
|
||||
return html`
|
||||
<button data-action="click:home-page#openModal">New Wallet</button>
|
||||
<button app-action="click:home-page#openModal">New Wallet</button>
|
||||
<wallet-header
|
||||
data-target="home-page.walletHeader"
|
||||
data-current-balance="0"
|
||||
|
||||
107
src/styles/app-dropdown/app-dropdown.scss
Normal file
107
src/styles/app-dropdown/app-dropdown.scss
Normal file
@@ -0,0 +1,107 @@
|
||||
.dropdown-custom {
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
div.dropdown-custom-top {
|
||||
position: relative;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid transparent;
|
||||
color: #09090a;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
padding: 5px 0;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
border: 1px solid #767676;
|
||||
border-radius: 3px;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
margin-right: 4px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-top: 8px solid #2e2e2e;
|
||||
}
|
||||
&.--open {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom: transparent;
|
||||
margin-bottom: 0 !important;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
margin-right: 4px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-bottom: 8px solid #2e2e2e;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
span.dropdown-custom-fieldname {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
div.dropdown-custom-open {
|
||||
border: 1px solid transparent;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
border-top: none;
|
||||
margin-top: 0 !important;
|
||||
background-color: #fbfafa;
|
||||
border-bottom-right-radius: 0.2em;
|
||||
border-bottom-left-radius: 0.2em;
|
||||
font-size: 16px;
|
||||
input.dropdown-custom-search {
|
||||
position: relative;
|
||||
width: calc(100% - 2 * 2px);
|
||||
margin: 2px;
|
||||
background-color: #fbfbfb;
|
||||
border: 1px solid #9c9c9c;
|
||||
border-radius: 0.3em;
|
||||
&:hover {
|
||||
border: 1px solid #b6b6b6;
|
||||
}
|
||||
}
|
||||
ul.dropdown-custom-list {
|
||||
padding: 1px 0;
|
||||
max-height: 100px;
|
||||
overflow-y: scroll;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
li.dropdown-custom-listitem {
|
||||
margin: 2px;
|
||||
padding: 1px 0;
|
||||
list-style-type: none;
|
||||
color: #0e0d0d;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #c6d8ff;
|
||||
}
|
||||
&.--selected {
|
||||
background-color: #d8e4ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/styles/app-dropdown/index.scss
Normal file
1
src/styles/app-dropdown/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import "./app-dropdown.scss";
|
||||
@@ -8,3 +8,4 @@
|
||||
@import "./page/index.scss";
|
||||
@import "./app-form/index.scss";
|
||||
@import "./layout/index.scss";
|
||||
@import "./app-dropdown/index.scss";
|
||||
|
||||
Reference in New Issue
Block a user