feat(StatusQ.Controls): introduce `StatusSelect`

This introduces a new `StatusSelect` component which is a select form control.
The `model` property can be used to apply a `ListModel` for dynamic data.
To give users full control over what the menu items look like, `StatusSelect`
exposes a `selectMenu.delegate` property.

Most of the time this should be a `StatusMenuItemDelegate` to get access to the
comple `MenuItem` component (remember that `StatusMenuItem` is merely an `Action`
type).

`StatusMenuItemDelegate` derives most of its behaviour by its applied `action`,
so the easiest way to construct a dynamic select with StatusQ menu item look and feel
is a combination of `StatusMenuItemDelegate` and `StatusMenuItem` as shown below.

Further more, because `StatusSelect` can't know what the `delegate` is going to look like
it also can't decide what data goes into a `selectedItem`. Therefore, it offers another API,
the `selectedItemComponent` which can be any component. This component can then be accessed
by menu item actions to set corresponding properties.

Usage:

```qml
import StatusQ.Controls 0.1

StatusSelect {
    label: "Some label"
    model: ListModel {
        ListElement {
            name: "Pascal"
        }
        ListElement {
            name: "Khushboo"
        }
        ListElement {
            name: "Alexandra"
        }
        ListElement {
            name: "Eric"
        }
    }

    selectMenu.delegate: StatusMenuItemDelegate {
        statusPopupMenu: select
        action: StatusMenuItem {
            iconSettings.name: "filled-account"
            text: name
            onTriggered: {
                selectedItem.text = name
            }
        }
    }

    selectedItemComponent: Item {
        id: selectedItem
        anchors.fill: parent
        property string text: ""

        StatusBaseText {
            text: selectedItem.text
            anchors.centerIn: parent
            color: Theme.palette.directColor1
        }
    }
}
```

Closes #436
This commit is contained in:
Pascal Precht 2021-10-12 17:29:40 +02:00 committed by Michał Cieślak
parent ec2aeffc55
commit 03c6da6e9f
8 changed files with 256 additions and 0 deletions

View File

@ -0,0 +1,54 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import Sandbox 0.1
Column {
spacing: 8
StatusSelect {
id: select
label: "Some label"
model: ListModel {
ListElement {
name: "Pascal"
}
ListElement {
name: "Khushboo"
}
ListElement {
name: "Alexandra"
}
ListElement {
name: "Eric"
}
}
selectMenu.delegate: StatusMenuItemDelegate {
statusPopupMenu: select
action: StatusMenuItem {
iconSettings.name: "filled-account"
text: name
onTriggered: {
selectedItem.text = name
}
}
}
selectedItemComponent: Item {
id: selectedItem
anchors.fill: parent
property string text: ""
StatusBaseText {
text: selectedItem.text
anchors.centerIn: parent
color: Theme.palette.directColor1
}
}
}
}

View File

@ -153,6 +153,11 @@ StatusWindow {
selected: page.sourceComponent == statusInputPageComponent selected: page.sourceComponent == statusInputPageComponent
onClicked: page.sourceComponent = statusInputPageComponent onClicked: page.sourceComponent = statusInputPageComponent
} }
StatusNavigationListItem {
title: "StatusSelect"
selected: page.sourceComponent == statusSelectPageComponent
onClicked: page.sourceComponent = statusSelectPageComponent
}
StatusListSectionHeadline { text: "StatusQ.Components" } StatusListSectionHeadline { text: "StatusQ.Components" }
StatusNavigationListItem { StatusNavigationListItem {
title: "List Items" title: "List Items"
@ -256,6 +261,11 @@ StatusWindow {
StatusInputPage {} StatusInputPage {}
} }
Component {
id: statusSelectPageComponent
StatusSelectPage {}
}
Component { Component {
id: listItemsComponent id: listItemsComponent
ListItems {} ListItems {}

View File

@ -0,0 +1,175 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
Item {
enum MenuAlignment {
Left,
Right,
Center
}
property string label: ""
readonly property bool hasLabel: label !== ""
property color bgColor: Theme.palette.baseColor2
readonly property int labelMargin: 7
property var model
property alias selectMenu: selectMenu
property color bgColorHover: bgColor
property alias selectedItemComponent: selectedItemContainer.children
property int caretRightMargin: 16
property alias select: inputRectangle
property int menuAlignment: StatusSelect.MenuAlignment.Right
property Item zeroItemsView: Item {}
property string validationError: ""
property alias validationErrorAlignment: validationErrorText.horizontalAlignment
property int validationErrorTopMargin: 11
implicitWidth: 448
id: root
height: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? (validationErrorText.height + validationErrorTopMargin) : 0)
StatusBaseText {
id: inputLabel
visible: hasLabel
text: root.label
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
font.pixelSize: 15
color: root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
}
Rectangle {
property bool hovered: false
id: inputRectangle
height: 56
color: hovered ? bgColorHover : bgColor
radius: 8
anchors.top: root.hasLabel ? inputLabel.bottom : parent.top
anchors.topMargin: root.hasLabel ? root.labelMargin : 0
anchors.right: parent.right
anchors.left: parent.left
border.width: !!validationError ? 1 : 0
border.color: Theme.palette.dangerColor1
Item {
id: selectedItemContainer
anchors.fill: parent
}
StatusIcon {
id: caret
anchors.right: parent.right
anchors.rightMargin: caretRightMargin
anchors.verticalCenter: parent.verticalCenter
width: 24
height: 24
icon: "chevron-down"
color: Theme.palette.baseColor1
}
}
Rectangle {
width: selectMenu.width
height: selectMenu.height
x: selectMenu.x
y: selectMenu.y
visible: selectMenu.opened
color: Theme.palette.statusSelect.menuItemBackgroundColor
radius: 8
border.color: Theme.palette.baseColor2
layer.enabled: true
layer.effect: DropShadow {
verticalOffset: 3
radius: 8
samples: 15
fast: true
cached: true
color: Theme.palette.dropShadow
}
}
StatusPopupMenu {
id: selectMenu
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
width: parent.width
clip: true
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)
}
}
}
StatusBaseText {
id: validationErrorText
visible: !!validationError
text: validationError
anchors.top: inputRectangle.bottom
anchors.topMargin: validationErrorTopMargin
anchors.right: parent.right
anchors.left: parent.left
font.pixelSize: 12
height: visible ? implicitHeight : 0
color: Theme.palette.dangerColor1
horizontalAlignment: TextEdit.AlignRight
wrapMode: Text.WordWrap
}
MouseArea {
id: mouseArea
anchors.fill: inputRectangle
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: {
inputRectangle.hovered = true
}
onExited: {
inputRectangle.hovered = false
}
onClicked: {
if (selectMenu.opened) {
selectMenu.close()
} else {
const rightOffset = inputRectangle.width - selectMenu.width
let offset = rightOffset
switch (root.menuAlignment) {
case StatusSelect.MenuAlignment.Left:
offset = 0
break
case StatusSelect.MenuAlignment.Right:
offset = rightOffset
break
case StatusSelect.MenuAlignment.Center:
offset = rightOffset / 2
}
selectMenu.popup(inputRectangle.x + offset, inputRectangle.y + inputRectangle.height + 8)
}
}
}
}

View File

@ -14,6 +14,7 @@ StatusSwitch 0.1 StatusSwitch.qml
StatusRadioButton 0.1 StatusRadioButton.qml StatusRadioButton 0.1 StatusRadioButton.qml
StatusCheckBox 0.1 StatusCheckBox.qml StatusCheckBox 0.1 StatusCheckBox.qml
StatusSlider 0.1 StatusSlider.qml StatusSlider 0.1 StatusSlider.qml
StatusSelect 0.1 StatusSelect.qml
StatusBaseInput 0.1 StatusBaseInput.qml StatusBaseInput 0.1 StatusBaseInput.qml
StatusInput 0.1 StatusInput.qml StatusInput 0.1 StatusInput.qml
StatusPickerButton 0.1 StatusPickerButton.qml StatusPickerButton 0.1 StatusPickerButton.qml

View File

@ -205,5 +205,10 @@ ThemePalette {
property QtObject statusSwitchTab: QtObject { property QtObject statusSwitchTab: QtObject {
property color backgroundColor: baseColor3 property color backgroundColor: baseColor3
} }
property QtObject statusSelect: QtObject {
property color menuItemBackgroundColor: baseColor2
property color menuItemHoverBackgroundColor: directColor7
}
} }

View File

@ -203,5 +203,10 @@ ThemePalette {
property QtObject statusSwitchTab: QtObject { property QtObject statusSwitchTab: QtObject {
property color backgroundColor: white property color backgroundColor: white
} }
property QtObject statusSelect: QtObject {
property color menuItemBackgroundColor: white
property color menuItemHoverBackgroundColor: baseColor2
}
} }

View File

@ -150,6 +150,11 @@ QtObject {
property color backgroundColor property color backgroundColor
} }
property QtObject statusSelect: QtObject {
property color menuItemBackgroundColor
property color menuItemHoeverBackgroundColor
}
function alphaColor(color, alpha) { function alphaColor(color, alpha) {
let actualColor = Qt.darker(color, 1) let actualColor = Qt.darker(color, 1)
actualColor.a = alpha actualColor.a = alpha

View File

@ -51,6 +51,7 @@
<file>src/StatusQ/Controls/StatusSwitch.qml</file> <file>src/StatusQ/Controls/StatusSwitch.qml</file>
<file>src/StatusQ/Controls/StatusCheckBox.qml</file> <file>src/StatusQ/Controls/StatusCheckBox.qml</file>
<file>src/StatusQ/Controls/StatusButton.qml</file> <file>src/StatusQ/Controls/StatusButton.qml</file>
<file>src/StatusQ/Controls/StatusSelect.qml</file>
<file>src/StatusQ/Controls/StatusToolTip.qml</file> <file>src/StatusQ/Controls/StatusToolTip.qml</file>
<file>src/StatusQ/Controls/Validators/StatusValidator.qml</file> <file>src/StatusQ/Controls/Validators/StatusValidator.qml</file>
<file>src/StatusQ/Controls/Validators/StatusMinLengthValidator.qml</file> <file>src/StatusQ/Controls/Validators/StatusMinLengthValidator.qml</file>