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
onClicked: page.sourceComponent = statusInputPageComponent
}
StatusNavigationListItem {
title: "StatusSelect"
selected: page.sourceComponent == statusSelectPageComponent
onClicked: page.sourceComponent = statusSelectPageComponent
}
StatusListSectionHeadline { text: "StatusQ.Components" }
StatusNavigationListItem {
title: "List Items"
@ -256,6 +261,11 @@ StatusWindow {
StatusInputPage {}
}
Component {
id: statusSelectPageComponent
StatusSelectPage {}
}
Component {
id: listItemsComponent
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
StatusCheckBox 0.1 StatusCheckBox.qml
StatusSlider 0.1 StatusSlider.qml
StatusSelect 0.1 StatusSelect.qml
StatusBaseInput 0.1 StatusBaseInput.qml
StatusInput 0.1 StatusInput.qml
StatusPickerButton 0.1 StatusPickerButton.qml

View File

@ -205,5 +205,10 @@ ThemePalette {
property QtObject statusSwitchTab: QtObject {
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 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 QtObject statusSelect: QtObject {
property color menuItemBackgroundColor
property color menuItemHoeverBackgroundColor
}
function alphaColor(color, alpha) {
let actualColor = Qt.darker(color, 1)
actualColor.a = alpha

View File

@ -51,6 +51,7 @@
<file>src/StatusQ/Controls/StatusSwitch.qml</file>
<file>src/StatusQ/Controls/StatusCheckBox.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/Validators/StatusValidator.qml</file>
<file>src/StatusQ/Controls/Validators/StatusMinLengthValidator.qml</file>