From 97ab18efff8e0265d0f2567abf3538c29354b785 Mon Sep 17 00:00:00 2001 From: santerisarle Date: Tue, 28 May 2024 17:36:11 +0300 Subject: [PATCH] Add modal styling --- .vscode/settings.json | 3 +- packages/ethereum-wallets/src/lib/modal.ts | 555 ++++++++++++++++----- 2 files changed, 430 insertions(+), 128 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a4a8998a1..ab2d991bc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,8 +2,7 @@ "eslint.format.enable": true, "prettier.enable": false, "editor.codeActionsOnSave": { - "source.fixAll.eslint": - true + "source.fixAll.eslint": "explicit" }, "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/packages/ethereum-wallets/src/lib/modal.ts b/packages/ethereum-wallets/src/lib/modal.ts index 80617f47b..3f1afd46a 100644 --- a/packages/ethereum-wallets/src/lib/modal.ts +++ b/packages/ethereum-wallets/src/lib/modal.ts @@ -12,66 +12,251 @@ export function createModal({ relayerPublicKey: string; }) { const modalStyles = ` - .ethereum-wallet-modal-container { + .ethereum-wallet-modal *, + .ethereum-wallet-modal *::before, + .ethereum-wallet-modal *::after { + box-sizing: border-box; + } + .ethereum-wallet-modal button { + cursor: pointer; + top: auto; + } + .ethereum-wallet-modal button:hover { + top: auto; + } + .ethereum-wallet-modal button:focus-visible { + top: auto; + } + + .ethereum-wallet-modal { display: none; - position: fixed; + position: relative; z-index: 9999; + } + .ethereum-wallet-modal-backdrop { + position: fixed; left: 0; + right: 0; top: 0; - width: 100%; - height: 100%; + bottom: 0; background-color: rgba(0, 0, 0, 0.5); - overflow: auto; + } + .ethereum-wallet-modal-wrapper { + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: 100vw; + overflow-y: auto; + } + .ethereum-wallet-modal-container { + display: flex; + min-height: 100%; + align-items: center; + justify-content: center; + padding: 1rem; + text-align: center; } .ethereum-wallet-modal-content { - background-color: #efefef; - border-radius: 10px; - margin: 2% auto; + position: relative; + overflow: hidden; + border-radius: 1rem; + background-color: #fff; padding: 20px; - width: 40%; + text-align: left; + max-width: 400px; + width: 100%; + box-shadow: + 0px 4px 8px rgba(0, 0, 0, 0.06), + 0px 0px 0px 1px rgba(0, 0, 0, 0.06); + } + + .ethereum-wallet-modal h2 { + font-weight: 700; + font-size: 24px; + line-height: 30px; text-align: center; - color: #4b4b4b; - word-wrap: break-word; - overflow-wrap: break-word; + letter-spacing: -0.1px; + color: #202020; + margin: 0; + } + + .ethereum-wallet-txs { + margin: 20px 0 10px 0; + display: flex; + flex-direction: column; + row-gap: 8px; } + .ethereum-wallet-tx { - margin-bottom: 10px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .ethereum-wallet-tx:not(.ethereum-wallet-tx-single) { + padding: 0 10px; + } + .ethereum-wallet-tx-list-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 10px; + width: 100%; + } + .ethereum-wallet-tx-list-header p { + margin: 0; + font-size: 14px; + line-height: 20px; + color: #202020; + font-weight: 700; + } + .ethereum-wallet-tx-list-header svg { + height: 24px; + width: 24px; + } + + .ethereum-wallet-tx.ethereum-wallet-tx-signing { + background-color: #F9F9F9; + padding-bottom: 10px; + border-radius: 8px; + } + .ethereum-wallet-tx.ethereum-wallet-tx-pending { + background-color: #F9F9F9; + border-radius: 8px; + } + .ethereum-wallet-tx.ethereum-wallet-tx-completed { + background-color: #F2FCF5; + border-radius: 8px; + } + + .ethereum-wallet-tx-single .ethereum-wallet-tx-info-container { + border-top: 1px solid #EBEBEB; + border-bottom: 1px solid #EBEBEB; + } + .ethereum-wallet-tx-info-text > p { + color: #646464; + margin: 0; + font-weight: 500; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.04px; + } + .ethereum-wallet-tx-info-text > p:not(:last-child) { + margin-bottom: 8px; + } + + .ethereum-wallet-tx-info-row { + padding: 14px 10px; + display: flex; + flex-direction: row; + justify-content: space-between; + column-gap: 20px; + } + .ethereum-wallet-tx-info-text { padding: 10px; - background-color: #efefef; - border-radius: 10px; - color: #4b4b4b; - border: solid; - border-color: #4b4b4b; - text-align: left - } - .ethereum-wallet-tx-completed { - background-color: #75ba8b; - color: #4b4b4b; - } - .ethereum-wallet-tx-signing { - background-color: #fce4a2; - color: #4b4b4b; - } - .cancel-ethereum-txs { - background: none; - border: none; - padding: 0; + } + .ethereum-wallet-tx-params, + .ethereum-wallet-tx-params dl { + margin: 0; + } + .ethereum-wallet-tx .ethereum-wallet-tx-params { + border-top: 1px solid #EBEBEB; + border-bottom: 1px solid #EBEBEB; + } + .ethereum-wallet-tx-params div:not(:last-child) { + border-bottom: 1px solid #EBEBEB; + } + .ethereum-wallet-tx-params dt { margin: 0; + font-size: 14px; + line-height: 20px; + color: #202020; + font-weight: 500; + } + .ethereum-wallet-tx-params dd { + margin: 0; + font-size: 14px; + line-height: 20px; + color: #202020; + font-weight: 700; + text-align: right; + word-break: break-all; + overflow-wrap: break-word; + } + + .ethereum-wallet-btn { + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + color: #1C2024; + border: 1px solid rgba(1, 6, 47, 0.173) !important; + background-color: #fff !important; + } + .ethereum-wallet-btn:hover { + border: 1px solid rgba(1, 6, 47, 0.173); + background-color: #F2F2F5 !important; + } + .ethereum-wallet-btn:focus-visible { + outline: 2px solid; + outline-offset: 2px; + outline-color: #8B8D98; + border: 1px solid rgba(1, 6, 47, 0.173); + } + .ethereum-wallet-btn-cancel { + padding: 14px; + font-size: 14px; + line-height: 20px; + font-weight: 700; + width: 100%; + } + .ethereum-wallet-btn-details { + margin-top: 20px; + padding: 8px 12px; + font-weight: 500; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.04px; + border-radius: 9999px; + } + + .ethereum-wallet-txs-details { + display: none; margin-top: 10px; - color: #4b4b4b; - font-size: inherit; - text-decoration: none; + padding: 10px; + background: #F1F1F1; + border-radius: 8px; + max-width: 100%; + overflow: auto; } - .cancel-ethereum-txs:hover { - text-decoration: underline; + .ethereum-wallet-txs-details p { + font-weight: 500; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.04px; + color: #646464; + word-wrap: break-word; + overflow-wrap: break-word; } - .cancel-ethereum-txs:active { - background: none; + + .ethereum-wallet-txs-status { + display: flex; + justify-content: center; + align-items: center; + padding: 14px; + background: #F1F4FE; + border-radius: 8px; + width: 100%; + margin-top: 24px; } - @media only screen and (max-width: 800px) { - .ethereum-wallet-modal-content { - width: 100%; /* Full width for smaller screens */ - } + .ethereum-wallet-txs-status p { + margin: 0; + color: #384EAC; + font-weight: 700; + font-size: 14px; + line-height: 20px; } `; @@ -80,11 +265,26 @@ export function createModal({ styleElement.textContent = modalStyles; window.document.head.appendChild(styleElement); - // Create modal container + // Container with display none/block const modalContainer = window.document.createElement("div"); - modalContainer.classList.add("ethereum-wallet-modal-container"); + modalContainer.classList.add("ethereum-wallet-modal"); + modalContainer.setAttribute("aria-labelledby", "modal-title"); + modalContainer.setAttribute("role", "dialog"); + modalContainer.setAttribute("aria-modal", "true"); + + // Backdrop + const backdrop = window.document.createElement("div"); + backdrop.classList.add("ethereum-wallet-modal-backdrop"); + + // Wrapper for modal + const modalWrapper = window.document.createElement("div"); + modalWrapper.classList.add("ethereum-wallet-modal-wrapper"); - // Create modal content + // Modal content container + const modalContentContainer = window.document.createElement("div"); + modalContentContainer.classList.add("ethereum-wallet-modal-container"); + + // Modal content const modalContent = window.document.createElement("div"); modalContent.classList.add("ethereum-wallet-modal-content"); modalContent.innerHTML = ` @@ -92,19 +292,24 @@ export function createModal({ txs.length === 1 && txs[0].actions.length === 1 && txs[0].actions[0].type === "AddKey" - ? "

Log in

" + ? "

Log in

" : txs.length === 1 && txs[0].actions.length === 1 && txs[0].actions[0].type === "DeleteKey" ? "

Log out

" - : `

Execute ${txs.length} transaction(s)

` + : `

Execute ${txs.length} transaction${ + txs.length > 1 ? "s" : "" + }

` }
- + `; - // Append modal content to modal container - modalContainer.appendChild(modalContent); + // // Append the elements to form the complete structure + modalContentContainer.appendChild(modalContent); + modalWrapper.appendChild(modalContentContainer); + modalContainer.appendChild(backdrop); + modalContainer.appendChild(modalWrapper); // Append modal container to document body window.document.body.appendChild(modalContainer); @@ -122,7 +327,7 @@ export function createModal({ // On cancel button click window.document - .querySelector(".cancel-ethereum-txs") + .querySelector(".ethereum-wallet-btn-cancel") ?.addEventListener("click", () => { onCancel(); hideModal(); @@ -135,93 +340,191 @@ export function createModal({ container.innerHTML = ""; txs.forEach((tx, i) => { + const txNumber = i + 1; + const singleTx = txs.length === 1; const txElement = document.createElement("div"); + txElement.classList.add("ethereum-wallet-tx"); - if (i < selectedIndex) { - txElement.classList.add("ethereum-wallet-tx-completed"); + if (singleTx) { + txElement.classList.add("ethereum-wallet-tx-single"); } - if (i === selectedIndex) { - txElement.classList.add("ethereum-wallet-tx-signing"); + + const isCompleted = i < selectedIndex; + const isActive = i === selectedIndex; + const isPending = i > selectedIndex; + + if (!singleTx) { + if (isCompleted) { + txElement.classList.add("ethereum-wallet-tx-completed"); + } + if (isActive) { + txElement.classList.add("ethereum-wallet-tx-signing"); + } + if (isPending) { + txElement.classList.add("ethereum-wallet-tx-pending"); + } } txElement.innerHTML = ` ${ - // Transaction description - tx.actions[0].type === "AddKey" - ? tx.actions[0].params.accessKey.permission === "FullAccess" - ? "

WARNING: The application is requesting a FullAccess key, you can loose your account and all your assets, only approve this transaction if you know what you are doing !!!

" - : tx.actions[0].params.accessKey.permission.allowance === "0" && - tx.actions[0].params.publicKey === relayerPublicKey && - tx.actions[0].params.accessKey.permission.receiverId === - tx.signerId && - tx.actions[0].params.accessKey.permission.methodNames - ?.length === 1 && - tx.actions[0].params.accessKey.permission.methodNames[0] === - RLP_EXECUTE - ? "

This transaction will onboard your account and enable you to transact on NEAR Protocol.

" - : ` -

Connect to ${ - tx.actions[0].params.accessKey.permission.receiverId - }

-

- Network Fee Allowance: ${ - tx.actions[0].params.accessKey.permission.allowance === - "0" - ? "unlimited" - : formatUnits( - BigInt( - tx.actions[0].params.accessKey.permission - .allowance ?? DEFAULT_ACCESS_KEY_ALLOWANCE - ), - 24 - ) - } NEAR -

- ${ - tx.actions[0].params.accessKey.permission.allowance === "0" - ? "

WARNING: this key will have unlimited allowance spendable towards network fees, only approve this transaction if you trust the application and you know what you are doing !!!

" - : "

This allowance is spendable by the application towards network fees incurred during use.

" - } - ` - : tx.actions[0].type === "DeleteKey" - ? "

This is an optional transaction which removes the application access key. If you reject the transaction, the key will be reused when you login again.

" - : tx.actions[0].type === "FunctionCall" + isCompleted ? ` -

Contract execution:

-

from: ${tx.signerId}

-

to: ${tx.receiverId}

-

function: ${tx.actions[0].params.methodName}

-

- deposit: ${formatUnits( - BigInt(tx.actions[0].params.deposit), - 24 - )} NEAR -

- ` - : tx.actions[0].type === "Transfer" +
+

Transaction ${txNumber}

+ +
+ ` + : isPending ? ` -

- Transfer ${formatUnits( - BigInt(tx.actions[0].params.deposit), - 24 - )} NEAR from ${tx.signerId} to ${tx.receiverId} -

+
+

Transaction ${txNumber}

+ +
+ ` + : ` + ${ + !singleTx + ? ` +
+

Transaction ${txNumber}

+ +
` - : "Unknown transaction type." + : "" + } +
+ ${ + tx.actions[0].type === "AddKey" + ? tx.actions[0].params.accessKey.permission === "FullAccess" + ? ` +
+

WARNING: The application is requesting a FullAccess key. You can lose your account and all your assets. Only approve this transaction if you know what you are doing!

+

` + : tx.actions[0].params.accessKey.permission.allowance === + "0" && + tx.actions[0].params.publicKey === relayerPublicKey && + tx.actions[0].params.accessKey.permission.receiverId === + tx.signerId && + tx.actions[0].params.accessKey.permission.methodNames + ?.length === 1 && + tx.actions[0].params.accessKey.permission + .methodNames[0] === RLP_EXECUTE + ? ` +
+

This transaction will onboard your account and enable you to transact on NEAR Protocol.

+
` + : ` +
+

Connect to ${ + tx.actions[0].params.accessKey.permission.receiverId + }

+

+ Network Fee Allowance: ${ + tx.actions[0].params.accessKey.permission + .allowance === "0" + ? "unlimited" + : formatUnits( + BigInt( + tx.actions[0].params.accessKey.permission + .allowance ?? DEFAULT_ACCESS_KEY_ALLOWANCE + ), + 24 + ) + } NEAR +

+ ${ + tx.actions[0].params.accessKey.permission + .allowance === "0" + ? "

WARNING: this key will have unlimited allowance spendable towards network fees, only approve this transaction if you trust the application and you know what you are doing!

" + : "

This allowance is spendable by the application towards network fees incurred during use.

" + } +
+ ` + : tx.actions[0].type === "DeleteKey" + ? ` +
+

This is an optional transaction which removes the application access key. If you reject the transaction, the key will be reused when you login again.

+
+ ` + : tx.actions[0].type === "FunctionCall" + ? ` +
+
+
From
+
${tx.signerId}
+
+
+
To
+
${tx.receiverId}
+
+
+
Type
+
${tx.actions[0].params.methodName}
+
+
+
Deposit
+
${formatUnits( + BigInt(tx.actions[0].params.deposit), + 24 + )} NEAR
+
+
+ ` + : tx.actions[0].type === "Transfer" + ? ` +
+

+ Transfer ${formatUnits( + BigInt(tx.actions[0].params.deposit), + 24 + )} NEAR from ${tx.signerId} to ${tx.receiverId} +

+
+ ` + : ` +
+

Unknown transaction type.

+
+ ` + } +
+ +
+

${JSON.stringify(tx.actions[0], null, 2)}

+
+
+

Sign the transaction in your wallet

+
+ ` } -

Transaction Details:

-

${JSON.stringify(tx.actions[0], null, 2)}

-

- Status: ${ - i < selectedIndex - ? "completed" - : i === selectedIndex - ? "sign the transaction in your wallet..." - : "pending..." - } -

+ `; + container.appendChild(txElement); }); + + const toggleButton = window.document.querySelector( + ".ethereum-wallet-btn-details" + ); + const detailsContainer = window.document.querySelector( + ".ethereum-wallet-txs-details" + ) as HTMLElement | null; + + toggleButton?.addEventListener("click", () => { + if (!detailsContainer || !toggleButton) { + return; + } + + if ( + detailsContainer.style.display === "none" || + detailsContainer.style.display === "" + ) { + detailsContainer.style.display = "block"; + toggleButton.textContent = "Hide details"; + } else { + detailsContainer.style.display = "none"; + toggleButton.textContent = "Show details"; + } + }); }; return { showModal, hideModal, renderTxs }; }