diff --git a/src/app/profile/views/contact_list.nim b/src/app/profile/views/contact_list.nim index 07cc7f76d7..d7fc3ea0b7 100644 --- a/src/app/profile/views/contact_list.nim +++ b/src/app/profile/views/contact_list.nim @@ -11,6 +11,8 @@ type Identicon = UserRole + 4 IsContact = UserRole + 5 IsBlocked = UserRole + 6 + Alias = UserRole + 7 + EnsVerified = UserRole + 8 QtObject: type ContactList* = ref object of QAbstractListModel @@ -48,6 +50,8 @@ QtObject: of "pubKey": result = contact.id of "isContact": result = $contact.isContact() of "isBlocked": result = $contact.isBlocked() + of "alias": result = contact.alias + of "ensVerified": result = $contact.ensVerified method data(self: ContactList, index: QModelIndex, role: int): QVariant = if not index.isValid: @@ -62,6 +66,8 @@ QtObject: of ContactRoles.PubKey: result = newQVariant(contact.id) of ContactRoles.IsContact: result = newQVariant(contact.isContact()) of ContactRoles.IsBlocked: result = newQVariant(contact.isBlocked()) + of ContactRoles.Alias: result = newQVariant(contact.alias) + of ContactRoles.EnsVerified: result = newQVariant(contact.ensVerified) method roleNames(self: ContactList): Table[int, string] = { @@ -70,7 +76,9 @@ QtObject: ContactRoles.Identicon.int:"identicon", ContactRoles.PubKey.int:"pubKey", ContactRoles.IsContact.int:"isContact", - ContactRoles.IsBlocked.int:"isBlocked" + ContactRoles.IsBlocked.int:"isBlocked", + ContactRoles.Alias.int:"alias", + ContactRoles.EnsVerified.int:"ensVerified" }.toTable proc addContactToList*(self: ContactList, contact: Profile) = diff --git a/ui/app/AppLayouts/Wallet/components/SendModalContent.qml b/ui/app/AppLayouts/Wallet/components/SendModalContent.qml index 078065a7f9..f9021f69cf 100644 --- a/ui/app/AppLayouts/Wallet/components/SendModalContent.qml +++ b/ui/app/AppLayouts/Wallet/components/SendModalContent.qml @@ -19,7 +19,7 @@ Item { return; } let result = walletModel.onSendTransaction(selectFromAccount.selectedAccount.address, - txtTo.text, + selectRecipient.selectedRecipient, selectAsset.selectedAsset.address, txtAmount.text, txtPassword.text) @@ -35,6 +35,7 @@ Item { } function validate() { + selectRecipient.validate() if (txtPassword.text === "") { //% "You need to enter a password" passwordValidationError = qsTrId("you-need-to-enter-a-password") @@ -45,16 +46,6 @@ Item { passwordValidationError = "" } - if (txtTo.text === "") { - //% "You need to enter a destination address" - toValidationError = qsTrId("you-need-to-enter-a-destination-address") - } else if (!Utils.isAddress(txtTo.text)) { - //% "This needs to be a valid address (starting with 0x)" - toValidationError = qsTrId("this-needs-to-be-a-valid-address-(starting-with-0x)") - } else { - toValidationError = "" - } - if (txtAmount.text === "") { //% "You need to enter an amount" amountValidationError = qsTrId("you-need-to-enter-an-amount") @@ -114,15 +105,15 @@ Item { } } - Input { - id: txtTo - //% "Recipient" - label: qsTrId("recipient") - //% "Send to" - placeholderText: qsTrId("send-to") + RecipientSelector { + id: selectRecipient + accounts: walletModel.accounts + contacts: profileModel.addedContacts + label: qsTr("Recipient") anchors.top: selectFromAccount.bottom anchors.topMargin: Style.current.padding - validationError: toValidationError + anchors.left: parent.left + anchors.right: parent.right } Input { @@ -131,7 +122,7 @@ Item { label: qsTrId("password") //% "Enter Password" placeholderText: qsTrId("biometric-auth-login-ios-fallback-label") - anchors.top: txtTo.bottom + anchors.top: selectRecipient.bottom anchors.topMargin: Style.current.padding textField.echoMode: TextInput.Password validationError: passwordValidationError diff --git a/ui/imports/Themes/DarkTheme.qml b/ui/imports/Themes/DarkTheme.qml index 5cc61e8eed..0e0ffa9cd7 100644 --- a/ui/imports/Themes/DarkTheme.qml +++ b/ui/imports/Themes/DarkTheme.qml @@ -5,6 +5,7 @@ Theme { property color white: "#FFFFFF" property color white2: "#FCFCFC" property color black: "#000000" + property color almostBlack: "#141414" property color grey: "#EEF2F5" property color lightBlue: "#ECEFFC" property color cyan: "#00FFFF" @@ -18,10 +19,15 @@ Theme { property color lightRed: "#FFEAEE" property color green: "#4EBC60" property color turquoise: "#007b7d" + property color tenPercentWhite: Qt.rgba(255, 255, 255, 0.1) + property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1) - property color background: "#141414" + property color background: almostBlack property color border: "#252528" + property color borderSecondary: tenPercentWhite + property color borderTertiary: blue property color textColor: white + property color textColorTertiary: blue property color currentUserTextColor: white property color secondaryBackground: "#23252F" property color inputBackground: secondaryBackground @@ -29,6 +35,9 @@ Theme { property color modalBackground: background property color backgroundHover: "#252528" property color secondaryText: darkGrey - property color secondaryHover: Qt.rgba(255, 255, 255, 0.1) + property color secondaryHover: tenPercentWhite property color danger: red + property color primaryMenuItemHover: blue + property color primaryMenuItemTextHover: almostBlack + property color backgroundTertiary: tenPercentBlue } diff --git a/ui/imports/Themes/LightTheme.qml b/ui/imports/Themes/LightTheme.qml index ffeb3ee1bb..567d324057 100644 --- a/ui/imports/Themes/LightTheme.qml +++ b/ui/imports/Themes/LightTheme.qml @@ -18,10 +18,15 @@ Theme { property color lightRed: "#FFEAEE" property color green: "#4EBC60" property color turquoise: "#007b7d" + property color tenPercentBlack: Qt.rgba(0, 0, 0, 0.1) + property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1) property color background: white property color border: grey + property color borderSecondary: tenPercentBlack + property color borderTertiary: blue property color textColor: black + property color textColorTertiary: blue property color currentUserTextColor: white property color secondaryBackground: lightBlue property color inputBackground: grey @@ -29,6 +34,9 @@ Theme { property color modalBackground: white2 property color backgroundHover: grey property color secondaryText: darkGrey - property color secondaryHover: Qt.rgba(0, 0, 0, 0.1) + property color secondaryHover: tenPercentBlack property color danger: red + property color primaryMenuItemHover: blue + property color primaryMenuItemTextHover: white + property color backgroundTertiary: tenPercentBlue } diff --git a/ui/shared/AccountSelector.qml b/ui/shared/AccountSelector.qml index 9c8a36ab67..a57a31972a 100644 --- a/ui/shared/AccountSelector.qml +++ b/ui/shared/AccountSelector.qml @@ -17,6 +17,7 @@ Item { // NOTE: if this asset is not selected as a wallet token in the UI, then // nothing will be displayed property string showAssetBalance: "" + property int dropdownWidth: width Repeater { visible: showAssetBalance !== "" @@ -36,7 +37,7 @@ Item { id: select label: root.label model: root.accounts - + menuAlignment: Select.MenuAlignment.Left menu.delegate: menuItem menu.onOpened: { selectedAccountDetails.visible = false @@ -44,6 +45,7 @@ Item { menu.onClosed: { selectedAccountDetails.visible = true } + menu.width: dropdownWidth selectedItemView: Item { anchors.fill: parent @@ -85,18 +87,11 @@ Item { StyledText { id: textSelectedAddress - text: selectedAccount.address + text: selectedAccount.address + " • " font.pixelSize: 12 elide: Text.ElideMiddle height: 16 - width: 85 - color: Style.current.secondaryText - } - StyledText { - id: separator - text: "• " - font.pixelSize: 12 - height: 16 + width: 90 color: Style.current.secondaryText } StyledText { diff --git a/ui/shared/AddressInput.qml b/ui/shared/AddressInput.qml new file mode 100644 index 0000000000..e04ec4e603 --- /dev/null +++ b/ui/shared/AddressInput.qml @@ -0,0 +1,89 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Layouts 1.13 +import QtGraphicalEffects 1.13 +import "../imports" + +Item { + id: root + property string validationError: "Error" + property alias label: inpAddress.label + property string selectedAddress + + height: inpAddress.height + + function isValidAddress(inputValue) { + return /0x[a-fA-F0-9]{40}/.test(inputValue) + } + function isValidEns(inputValue) { + // TODO: Check if the entered value resolves to an address. Long operation. + // Issue tracked: https://github.com/status-im/nim-status-client/issues/718 + const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue) + const isDomain = /(?:(?:(?[\w\-]*)(?:\.))?(?[\w\-]*))\.(?[\w\-]*)/.test(inputValue) + return isEmail || isDomain + } + + function validate(inputValue) { + if (!inputValue) inputValue = selectedAddress + let isValid = + (inputValue && inputValue.startsWith("0x") && isValidAddress(inputValue)) || + isValidEns(inputValue) + inpAddress.validationError = isValid ? "" : validationError + return isValid + } + + Input { + id: inpAddress + placeholderText: qsTr("eg. 0x1234 or ENS") + customHeight: 56 + validationErrorAlignment: TextEdit.AlignRight + validationErrorTopMargin: 8 + textField.onFocusChanged: { + let isValid = true + if (text !== "") { + isValid = root.validate(metrics.text) + } + if (!isValid) { + return + } + if (textField.focus) { + text = metrics.text + } else if (root.isValidAddress(metrics.text)) { + text = metrics.elidedText + } + } + textField.rightPadding: 73 + onTextEdited: { + metrics.text = text + const isValid = root.validate(inputValue) + if (isValid) { + root.selectedAddress = inputValue + } + } + TextMetrics { + id: metrics + elideWidth: 97 + elide: Text.ElideMiddle + } + TertiaryButton { + anchors.right: parent.right + anchors.rightMargin: 8 + anchors.top: parent.top + anchors.topMargin: 14 + label: qsTr("Paste") + onClicked: { + if (inpAddress.textField.canPaste) { + inpAddress.textField.paste() + } + } + } + } +} + + + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/ui/shared/AddressSourceSelector.qml b/ui/shared/AddressSourceSelector.qml new file mode 100644 index 0000000000..3430eca8b0 --- /dev/null +++ b/ui/shared/AddressSourceSelector.qml @@ -0,0 +1,67 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Layouts 1.13 +import QtGraphicalEffects 1.13 +import "../imports" + +Item { + id: root + property var sources: [] + property string selectedSource: sources[0] || "Address" + property int dropdownWidth: 220 + height: select.height + + Select { + id: select + anchors.left: parent.left + anchors.right: parent.right + model: root.sources + selectedItemView: Item { + anchors.fill: parent + StyledText { + id: selectedTextField + text: root.selectedSource + anchors.left: parent.left + anchors.leftMargin: Style.current.padding + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: 15 + verticalAlignment: Text.AlignVCenter + height: 24 + } + } + menu.width: dropdownWidth + menu.topPadding: 8 + menu.bottomPadding: 8 + menu.delegate: Component { + MenuItem { + id: menuItem + height: 40 + width: parent.width + onTriggered: function () { + root.selectedSource = root.sources[index] + } + + StyledText { + id: itemText + text: root.sources[index] + anchors.left: parent.left + anchors.leftMargin: Style.current.padding + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: 15 + height: 22 + color: menuItem.highlighted ? Style.current.primaryMenuItemTextHover : Style.current.textColor + } + background: Rectangle { + color: menuItem.highlighted ? Style.current.primaryMenuItemHover : Style.current.transparent + } + } + } + } +} + + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/ui/shared/ContactSelector.qml b/ui/shared/ContactSelector.qml new file mode 100644 index 0000000000..131cb6b455 --- /dev/null +++ b/ui/shared/ContactSelector.qml @@ -0,0 +1,186 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Layouts 1.13 +import QtGraphicalEffects 1.13 +import "../imports" + +Item { + id: root + property var contacts + property var selectedContact + height: select.height + (validationErrorText.visible ? validationErrorText.height : 0) + property int dropdownWidth: width + property string validationError: validationErrorText.text + property alias validationErrorAlignment: validationErrorText.horizontalAlignment + + onContactsChanged: { + root.selectedContact = { name: qsTr("Select a contact") } + } + + function validate() { + const isValid = root.selectedContact && root.selectedContact.address + if (!isValid) { + select.select.border.color = Style.current.danger + select.select.border.width = 1 + validationErrorText.visible = true + } else { + select.select.border.color = Style.current.transparent + select.select.border.width = 0 + validationErrorText.visible = false + } + } + + Select { + id: select + label: "" + model: root.contacts + width: parent.width + menuAlignment: Select.MenuAlignment.Left + selectedItemView: Item { + anchors.fill: parent + Identicon { + id: iconImg + anchors.left: parent.left + anchors.leftMargin: 14 + anchors.verticalCenter: parent.verticalCenter + height: 32 + width: !!selectedContact.identicon ? 32 : 0 + visible: !!selectedContact.identicon + source: selectedContact.identicon ? selectedContact.identicon : "" + } + + StyledText { + id: selectedTextField + text: selectedContact.name + anchors.left: iconImg.right + anchors.leftMargin: 4 + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: 15 + height: 22 + verticalAlignment: Text.AlignVCenter + } + } + zeroItemsView: Item { + height: 186 + StyledText { + anchors.fill: parent + text: qsTr("You don't have any contacts yet") + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 13 + height: 18 + color: Style.current.secondaryText + } + } + + menu.delegate: menuItem + menu.width: dropdownWidth + } + TextEdit { + id: validationErrorText + visible: false + text: qsTr("Please select a contact") + anchors.top: select.bottom + anchors.topMargin: 8 + selectByMouse: true + readOnly: true + font.pixelSize: 12 + height: 16 + color: Style.current.danger + width: parent.width + horizontalAlignment: TextEdit.AlignRight + } + + Component { + id: menuItem + MenuItem { + id: itemContainer + property bool isFirstItem: index === 0 + property bool isLastItem: index === contacts.rowCount() - 1 + + width: parent.width + height: visible ? 72 : 0 + Identicon { + id: iconImg + anchors.left: parent.left + anchors.leftMargin: Style.current.padding + anchors.verticalCenter: parent.verticalCenter + width: 40 + height: 40 + source: identicon + } + Column { + anchors.left: iconImg.right + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + + Text { + text: name + font.pixelSize: 15 + font.family: Style.current.fontBold.name + font.bold: true + color: Style.current.textColor + height: 22 + } + + Row { + StyledText { + text: alias + " • " + visible: ensVerified + color: Style.current.secondaryText + font.pixelSize: 12 + height: 16 + } + StyledText { + text: address + width: 85 + elide: Text.ElideMiddle + color: Style.current.secondaryText + font.pixelSize: 12 + height: 16 + } + } + } + background: Rectangle { + color: itemContainer.highlighted ? Style.current.backgroundHover : Style.current.background + radius: Style.current.radius + + // cover bottom left/right corners with square corners + Rectangle { + visible: !isLastItem + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: parent.radius + color: parent.color + } + + // cover top left/right corners with square corners + Rectangle { + visible: !isFirstItem + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: parent.radius + color: parent.color + } + } + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: itemContainer + onClicked: { + root.selectedContact = { address, name, alias, isContact, identicon, ensVerified } + select.menu.close() + validate() + } + } + } + } +} + + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/ui/shared/Identicon.qml b/ui/shared/Identicon.qml index ef07b54332..20c3b5014e 100644 --- a/ui/shared/Identicon.qml +++ b/ui/shared/Identicon.qml @@ -9,7 +9,7 @@ Rectangle { color: Style.current.background radius: 50 border.width: 1 - border.color: Style.current.border + border.color: Style.current.borderSecondary Image { width: parent.width diff --git a/ui/shared/Input.qml b/ui/shared/Input.qml index ca0e7de0ca..9b73dd9e3a 100644 --- a/ui/shared/Input.qml +++ b/ui/shared/Input.qml @@ -5,8 +5,11 @@ import "../imports" Item { property alias textField: inputValue property string placeholderText: "My placeholder" + property string placeholderTextColor: Style.current.secondaryText property alias text: inputValue.text property string validationError: "" + property alias validationErrorAlignment: validationErrorText.horizontalAlignment + property int validationErrorTopMargin: 1 property string label: "" readonly property bool hasLabel: label !== "" property color bgColor: Style.current.inputBackground @@ -22,9 +25,10 @@ Item { property int customHeight: 44 property int fontPixelSize: 15 signal editingFinished(string inputValue) + signal textEdited(string inputValue) id: inputBox - height: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? validationErrorText.height : 0) + height: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? (validationErrorText.height + validationErrorTopMargin) : 0) anchors.right: parent.right anchors.left: parent.left @@ -56,6 +60,7 @@ Item { id: inputValue visible: !inputBox.isTextArea && !inputBox.isSelect placeholderText: inputBox.placeholderText + placeholderTextColor: inputBox.placeholderTextColor text: inputBox.text anchors.top: parent.top anchors.topMargin: 0 @@ -72,6 +77,7 @@ Item { color: Style.current.transparent } onEditingFinished: inputBox.editingFinished(inputBox.text) + onTextEdited: inputBox.textEdited(inputBox.text) } SVGImage { @@ -91,12 +97,13 @@ Item { id: validationErrorText text: validationError anchors.top: inputRectangle.bottom - anchors.topMargin: 1 + anchors.topMargin: validationErrorTopMargin selectByMouse: true readOnly: true font.pixelSize: 12 - color: Style.current.red - + height: 16 + color: Style.current.danger + width: parent.width } } diff --git a/ui/shared/PopupMenu.qml b/ui/shared/PopupMenu.qml index 514a201ad6..30d9a99042 100644 --- a/ui/shared/PopupMenu.qml +++ b/ui/shared/PopupMenu.qml @@ -82,8 +82,8 @@ Menu { anchors.fill: parent source: parent color: popupMenuItem.highlighted ? - Style.current.white : - (popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.blue) + Style.current.primaryMenuItemTextHover : + (popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.primaryMenuItemHover) } } @@ -92,7 +92,7 @@ Menu { anchors.leftMargin: popupMenu.paddingSize text: popupMenuItem.text font: popupMenuItem.font - color: popupMenuItem.highlighted ? Style.current.white : popupMenuItem.textColor + color: popupMenuItem.highlighted ? Style.current.primaryMenuItemTextHover : popupMenuItem.textColor horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter opacity: enabled ? 1.0 : 0.3 diff --git a/ui/shared/RecipientSelector.qml b/ui/shared/RecipientSelector.qml new file mode 100644 index 0000000000..a309d8eb42 --- /dev/null +++ b/ui/shared/RecipientSelector.qml @@ -0,0 +1,121 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Layouts 1.13 +import QtGraphicalEffects 1.13 +import "../imports" + +Item { + id: root + property var accounts + property var contacts + property int inputWidth: 272 + property int sourceSelectWidth: 136 + property string label: qsTr("Recipient") + property string selectedRecipient: "" + height: inpAddress.height + txtLabel.height + + function validate() { + if (selAddressSource.selectedSource === "Address") { + inpAddress.validate() + } else if (selAddressSource.selectedSource === "Contact") { + selContact.validate() + } + } + + Text { + id: txtLabel + visible: label !== "" + text: root.label + font.pixelSize: 13 + font.family: Style.current.fontBold.name + color: Style.current.textColor + height: 18 + } + + RowLayout { + anchors.top: txtLabel.bottom + anchors.topMargin: 7 + anchors.left: parent.left + anchors.right: parent.right + spacing: 8 + + AddressInput { + id: inpAddress + width: root.inputWidth + label: "" + Layout.preferredWidth: root.inputWidth + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + validationError: qsTr("Invalid ethereum address") + onSelectedAddressChanged: { + root.selectedRecipient = selectedAddress + } + } + + ContactSelector { + id: selContact + contacts: root.contacts + visible: false + width: root.inputWidth + dropdownWidth: parent.width + Layout.preferredWidth: root.inputWidth + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + onSelectedContactChanged: { + if(selectedContact && selectedContact.address) { + root.selectedRecipient = selectedContact.address + } + } + } + + AccountSelector { + id: selAccount + accounts: root.accounts + visible: false + width: root.inputWidth + dropdownWidth: parent.width + label: "" + Layout.preferredWidth: root.inputWidth + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + onSelectedAccountChanged: { + root.selectedRecipient = selectedAccount.address + } + } + AddressSourceSelector { + id: selAddressSource + sources: ["Address", "Contact", "My account"] + width: sourceSelectWidth + Layout.preferredWidth: root.sourceSelectWidth + Layout.alignment: Qt.AlignTop + + onSelectedSourceChanged: { + switch (selectedSource) { + case "Address": + inpAddress.visible = true + selContact.visible = selAccount.visible = false + root.height = Qt.binding(function() { return inpAddress.height + txtLabel.height }) + break; + case "Contact": + selContact.visible = true + inpAddress.visible = selAccount.visible = false + root.height = Qt.binding(function() { return selContact.height + txtLabel.height }) + break; + case "My account": + selAccount.visible = true + inpAddress.visible = selContact.visible = false + root.height = Qt.binding(function() { return selAccount.height + txtLabel.height }) + break; + } + } + } + } + +} + + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/ diff --git a/ui/shared/Select.qml b/ui/shared/Select.qml index 5db4861b07..82c6b515d0 100644 --- a/ui/shared/Select.qml +++ b/ui/shared/Select.qml @@ -5,6 +5,10 @@ import QtGraphicalEffects 1.13 import "../imports" Item { + enum MenuAlignment { + Left, + Right + } property string label: "" readonly property bool hasLabel: label !== "" property color bgColor: Style.current.inputBackground @@ -15,6 +19,8 @@ Item { property alias selectedItemView: selectedItemContainer.children property int caretRightMargin: Style.current.padding property alias select: inputRectangle + property int menuAlignment: Select.MenuAlignment.Right + property Item zeroItemsView: Item {} anchors.left: parent.left anchors.right: parent.right @@ -105,7 +111,24 @@ Item { Repeater { id: menuItems model: root.model + property int zeroItemsViewHeight delegate: selectMenu.delegate + onItemAdded: { + root.zeroItemsView.visible = false + root.zeroItemsView.height = 0 + } + onItemRemoved: { + if (count === 0) { + root.zeroItemsView.visible = true + root.zeroItemsView.height = zeroItemsViewHeight + } + } + Component.onCompleted: { + zeroItemsViewHeight = root.zeroItemsView.height + root.zeroItemsView.visible = count === 0 + root.zeroItemsView.height = count !== 0 ? 0 : root.zeroItemsView.height + selectMenu.insertItem(0, root.zeroItemsView) + } } } MouseArea { @@ -123,7 +146,8 @@ Item { if (selectMenu.opened) { selectMenu.close() } else { - const offset = inputRectangle.width - selectMenu.width + const rightOffset = inputRectangle.width - selectMenu.width + const offset = root.menuAlignment === Select.MenuAlignment.Left ? 0 : rightOffset selectMenu.popup(inputRectangle.x + offset, inputRectangle.y + inputRectangle.height + 8) } } diff --git a/ui/shared/TertiaryButton.qml b/ui/shared/TertiaryButton.qml new file mode 100644 index 0000000000..6bab5f0fc4 --- /dev/null +++ b/ui/shared/TertiaryButton.qml @@ -0,0 +1,38 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import "../imports" + +Button { + id: root + property alias label: txtBtnLabel.text + + width: txtBtnLabel.width + 2 * 12 + height: txtBtnLabel.height + 2 * 6 + + background: Rectangle { + color: Style.current.backgroundTertiary + radius: 6 + anchors.fill: parent + border.color: Style.current.borderTertiary + border.width: 1 + } + + StyledText { + id: txtBtnLabel + color: Style.current.textColorTertiary + font.pixelSize: 12 + height: 16 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Paste") + } + + MouseArea { + id: mouse + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: { + parent.clicked() + } + } +} \ No newline at end of file