feat: New date picker for Status desktop

- aligns the StatusDatePicker component with the latest UI/UX
- adds a storybook page to explore the options

Closes #10862
This commit is contained in:
Lukáš Tinkl 2023-05-30 23:53:54 +02:00 committed by Iuri Matias
parent 0655ad4ea3
commit c3b1a5f2ab
7 changed files with 281 additions and 89 deletions

View File

@ -297,6 +297,10 @@ ListModel {
title: "StatusBlockProgressBar"
section: "Components"
}
ListElement {
title: "StatusDatePicker"
section: "Components"
}
ListElement {
title: "StatusDateRangePicker"
section: "Components"

View File

@ -0,0 +1,71 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import StatusQ.Components 0.1
import Storybook 1.0
SplitView {
Logs { id: logs }
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
Item {
Layout.fillWidth: true
SplitView.preferredHeight: 200
StatusDatePicker {
id: picker
anchors.centerIn: parent
onSelectedDateChanged: logs.logEvent("Selected date: %1".arg(picker.selectedDate.toISOString()))
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
logsView.logText: logs.logText
ColumnLayout {
width: parent.width
Button {
text: "Select today"
onClicked: picker.selectedDate = new Date()
}
Switch {
text: "Week numbers"
checked: picker.weekNumbersVisible
onToggled: {
picker.weekNumbersVisible = checked
logs.logEvent("Week numbers shown: %1".arg(checked ? "true" : "false"))
}
}
Switch {
text: "Short format"
checked: picker.dateFormat === Locale.ShortFormat
onToggled: {
picker.dateFormat = checked ? Locale.ShortFormat : Locale.LongFormat
logs.logEvent("Using short date format: %1".arg(checked ? "true" : "false"))
}
}
TextField {
placeholderText: "Custom \"today\" text"
onEditingFinished: {
picker.customTodayText = text
logs.logEvent("Custom \"Today\" text: %1".arg(picker.customTodayText))
}
}
TextField {
placeholderText: "Locale code ('fr'); empty = default"
onEditingFinished: picker.control.locale = Qt.locale(text)
}
}
}
}
}

View File

@ -34,8 +34,8 @@ SplitView {
toTimestamp: Date.now()
supportedStartYear: 1900
onNewRangeSet: {
console.log(" from timeStamp = ",fromTimestamp)
console.log(" to timeStamp = ",toTimestamp)
console.warn(" from timeStamp = ", new Date(fromTimestamp).toISOString())
console.warn(" to timeStamp = ", new Date(toTimestamp).toISOString())
}
}

View File

@ -1,6 +1,6 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.calendar 1.0
@ -24,7 +24,7 @@ StatusComboBox {
id: root
/*!
\qmlproperty string StatusDatePicker::dateFormat
\qmlproperty int StatusDatePicker::dateFormat
This property specifies how the selected date will be displayed to the user.
Either Locale.ShortFormat (default) or Locale.LongFormat
@ -34,152 +34,256 @@ StatusComboBox {
/*!
\qmlproperty date StatusDatePicker::selectedDate
This property holds the currently selected date.
This property holds the currently selected date; can be used for selecting an initial date too; defaults to "today"
\sa QtQml.Date
*/
property alias selectedDate: d.selectedDate
/*!
\qmlproperty bool StatusDatePicker::weekNumbersVisible
This property holds whether the week number column should be visible. Defaults to true
*/
property bool weekNumbersVisible: true
/*!
\qmlproperty string StatusDatePicker::customTodayText
This property holds a special value which to display in case the current day is selected; defaults to qsTr("Today").
Set to empty string if "today" shouldn't display anything special
*/
property string customTodayText: qsTr("Today")
QtObject {
id: d
property date selectedDate: new Date()
property int month: d.selectedDate.getMonth()
property int year: d.selectedDate.getFullYear()
readonly property bool selectingMonth: stackView.depth > 1
readonly property date today: new Date()
readonly property bool isTodaySelected: {
return today.getDate() === d.selectedDate.getDate() &&
today.getMonth() === d.selectedDate.getMonth() &&
today.getFullYear() === d.selectedDate.getFullYear()
}
}
implicitHeight: 44
indicatorIcon: "calendar"
control.delegate: null
control.displayText: LocaleUtils.formatDate(root.selectedDate, root.dateFormat)
control.popup.horizontalPadding: 8
control.displayText: d.isTodaySelected && root.customTodayText ? root.customTodayText
: LocaleUtils.formatDate(d.selectedDate, root.dateFormat)
control.popup.horizontalPadding: 16
control.popup.verticalPadding: 16
control.popup.width: 340
control.popup.onAboutToShow: {
// always open the popup showing the last (currently) selected date
grid.year = d.selectedDate.getFullYear()
grid.month = d.selectedDate.getMonth()
d.year = d.selectedDate.getFullYear()
d.month = d.selectedDate.getMonth()
}
control.popup.contentItem: Item {
property alias grid: grid
GridLayout {
LayoutMirroring.enabled: control.locale.textDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
WheelHandler {
orientation: Qt.Vertical
onWheel: {
const delta = event.angleDelta.y/-120
d.selectingMonth ? d.year += delta : d.month += delta
}
}
ColumnLayout {
anchors.fill: parent
columns: 2
spacing: 12
RowLayout {
Layout.fillWidth: true
Layout.columnSpan: 2
Layout.preferredHeight: 44
spacing: 0
StatusFlatButton {
leftPadding: 8
rightPadding: 8
text: "<<"
onClicked: grid.year = grid.year - 1
StatusToolTip {
text: qsTr("Previous year")
visible: parent.hovered
}
}
StatusFlatButton {
leftPadding: 8
rightPadding: 8
text: "<"
Layout.fillHeight: true
Layout.preferredWidth: height
icon.name: LayoutMirroring.enabled ? "next" : "previous"
icon.color: Theme.palette.directColor1
hoverColor: Theme.palette.statusMenu.hoverBackgroundColor
onClicked: {
// switch to previous month (and optionally prev year in January)
const date = new Date(grid.year, grid.month)
date.setMonth(date.getMonth() - 1)
grid.month = date.getMonth()
grid.year = date.getFullYear()
// switch to previous month/year (and optionally prev year in January)
const date = new Date(d.year, d.month)
if (d.selectingMonth)
date.setFullYear(date.getFullYear() - 1)
else
date.setMonth(date.getMonth() - 1)
d.month = date.getMonth()
d.year = date.getFullYear()
}
StatusToolTip {
text: qsTr("Previous month")
text: d.selectingMonth ? qsTr("Previous year") : qsTr("Previous month")
visible: parent.hovered
}
}
Item { Layout.fillWidth: true }
StatusFlatButton {
Layout.fillWidth: true
text: grid.title
font.pixelSize: 18
Layout.fillHeight: true
text: d.selectingMonth ? d.year : "%1 %2".arg(control.locale.standaloneMonthName(d.month)).arg(d.year)
font.weight: Font.Medium
textColor: Theme.palette.directColor1
hoverColor: Theme.palette.statusMenu.hoverBackgroundColor
borderColor: hoverColor
onClicked: {
const date = new Date()
grid.month = date.getMonth()
grid.year = date.getFullYear()
if (d.selectingMonth) {
const thisYear = d.today.getFullYear()
if (d.year !== thisYear)
d.year = thisYear // switch to current year
else
stackView.pop(null, StackView.Immediate) // switch back to regular calendar
} else {
stackView.push(yearMonthsComponent, StackView.Immediate) // switch to year/month selection
}
}
StatusToolTip {
text: qsTr("Show current month")
text: qsTr("Select year/month")
visible: parent.hovered
}
}
Item { Layout.fillWidth: true }
StatusFlatButton {
leftPadding: 8
rightPadding: 8
text: ">"
Layout.fillHeight: true
Layout.preferredWidth: height
icon.name: LayoutMirroring.enabled ? "previous" : "next"
icon.color: Theme.palette.directColor1
hoverColor: Theme.palette.statusMenu.hoverBackgroundColor
onClicked: {
// switch to next month (and optionally next year in December)
const date = new Date(grid.year, grid.month)
date.setMonth(date.getMonth() + 1)
grid.month = date.getMonth()
grid.year = date.getFullYear()
// switch to next month/year (and optionally next year in December)
const date = new Date(d.year, d.month)
if (d.selectingMonth)
date.setFullYear(date.getFullYear() + 1)
else
date.setMonth(date.getMonth() + 1)
d.month = date.getMonth()
d.year = date.getFullYear()
}
StatusToolTip {
text: qsTr("Next month")
visible: parent.hovered
}
}
StatusFlatButton {
leftPadding: 8
rightPadding: 8
text: ">>"
onClicked: grid.year = grid.year + 1
StatusToolTip {
text: qsTr("Next year")
text: d.selectingMonth ? qsTr("Next year") : qsTr("Next month")
visible: parent.hovered
}
}
}
DayOfWeekRow {
Layout.row: 1
Layout.column: 1
StackView {
id: stackView
Layout.fillWidth: true
Layout.preferredHeight: currentItem.implicitHeight
initialItem: singleMonthGridComponent
}
}
}
Component {
id: singleMonthGridComponent
GridLayout {
columns: 2
DayOfWeekRow {
Layout.fillWidth: true
Layout.preferredHeight: 44
Layout.column: root.weekNumbersVisible ? 1 : 0
Layout.columnSpan: root.weekNumbersVisible ? 1 : 2
spacing: 2
background: Rectangle {
color: Theme.palette.baseColor4
radius: 8
}
delegate: StatusBaseText {
text: model.shortName
font.weight: Font.Light
color: Theme.palette.directColor3
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
locale: control.locale
}
WeekNumberColumn {
id: weekColumn
month: grid.month
year: grid.year
Layout.fillHeight: true
month: d.month
year: d.year
visible: root.weekNumbersVisible
spacing: 2
background: Rectangle {
color: Theme.palette.baseColor4
radius: 8
}
delegate: StatusBaseText {
text: model.weekNumber
font.weight: Font.Light
color: Theme.palette.directColor3
text: model.weekNumber
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
locale: control.locale
}
MonthGrid {
id: grid
month: d.selectedDate.getMonth()
year: d.selectedDate.getFullYear()
Layout.fillWidth: true
Layout.fillHeight: true
month: d.month
year: d.year
spacing: 2
delegate: StatusFlatButton {
readonly property bool selected: d.selectedDate.getFullYear() === model.year &&
d.selectedDate.getMonth() === model.month &&
d.selectedDate.getDate() === model.day
horizontalPadding: 4
opacity: model.month === grid.month ? 1 : 0.5
radius: width/2
width: 40
height: 40
horizontalPadding: 0
verticalPadding: 0
text: model.day
textColor: selected ? Theme.palette.primaryColor1 : Theme.palette.directColor1
font.bold: selected || model.today
normalColor: selected ? Theme.palette.primaryColor1 : "transparent"
textColor: selected && !hovered ? "white" : model.month === d.month ? Theme.palette.directColor1
: Theme.palette.baseColor1
borderColor: model.today && !selected ? Theme.palette.directColor9 : "transparent"
onClicked: {
d.selectedDate = model.date
d.selectedDate = model.today ? new Date() : model.date
root.control.popup.close()
}
}
locale: control.locale
}
}
}
Component {
id: yearMonthsComponent
Item {
implicitHeight: childrenRect.height
Grid {
anchors.centerIn: parent
columns: 3
rowSpacing: 28
columnSpacing: 17
Repeater {
model: 12
delegate: StatusFlatButton {
readonly property bool selected: d.selectedDate.getMonth() === index && d.selectedDate.getFullYear() === d.year
readonly property bool currentMonth: d.today.getMonth() === index && d.today.getFullYear() === d.year
radius: height/2
normalColor: selected ? Theme.palette.primaryColor1 : "transparent"
textColor: selected && !hovered ? "white" : Theme.palette.directColor1
borderColor: currentMonth && !selected ? Theme.palette.directColor9 : "transparent"
text: control.locale.standaloneMonthName(index, Locale.ShortFormat)
onClicked: {
d.month = index
stackView.pop(null, StackView.Immediate)
}
}
}
}
}
}

View File

@ -6,9 +6,6 @@ import QtGraphicalEffects 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import QtQuick.Templates 2.14 as T
Item {
id: root
@ -25,6 +22,7 @@ Item {
property alias validationError: validationErrorItem.text
property string popupContentItemObjectName: ""
property string indicatorIcon: "chevron-down"
property int size: StatusComboBox.Size.Large
property int type: StatusComboBox.Type.Primary
@ -74,16 +72,14 @@ Item {
spacing: 16
background: Rectangle {
implicitHeight: 24 + comboBox.topPadding + comboBox.bottomPadding
implicitWidth: 448
color: root.type === StatusComboBox.Type.Secondary ? "transparent" : Theme.palette.baseColor2
radius: 8
border.width: (!!root.validationError || comboBox.hovered || comboBox.down || comboBox.activeFocus || root.type === StatusComboBox.Type.Secondary) ? 1 : 0
border.width: (!!root.validationError || comboBox.hovered || comboBox.down || comboBox.visualFocus || root.type === StatusComboBox.Type.Secondary) ? 1 : 0
border.color: {
if (!!root.validationError)
return Theme.palette.dangerColor1
if (comboBox.activeFocus)
if (comboBox.visualFocus || comboBox.popup.opened)
return Theme.palette.primaryColor1
if (comboBox.hovered)
@ -116,8 +112,16 @@ Item {
y: comboBox.topPadding + (comboBox.availableHeight - height) / 2
width: root.size === StatusComboBox.Size.Large ? 24 : 16
height: width
icon: "chevron-down"
color: Theme.palette.baseColor1
icon: root.indicatorIcon
color: {
if (comboBox.visualFocus || comboBox.popup.opened)
return Theme.palette.primaryColor1
if (comboBox.hovered)
return Theme.palette.primaryColor2
return Theme.palette.baseColor1
}
}
popup: Popup {
@ -133,7 +137,6 @@ Item {
verticalPadding: 8
background: Rectangle {
id: backgroundItem
color: Theme.palette.statusSelect.menuItemBackgroundColor
radius: 8
border.color: Theme.palette.baseColor2

View File

@ -142,6 +142,7 @@
<file>assets/img/icons/bold.svg</file>
<file>assets/img/icons/bridge.svg</file>
<file>assets/img/icons/browser.svg</file>
<file>assets/img/icons/calendar.svg</file>
<file>assets/img/icons/camera.svg</file>
<file>assets/img/icons/cancel.svg</file>
<file>assets/img/icons/channel-category.svg</file>

View File

@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.25 2.25C16.25 2.52614 16.0261 2.75 15.75 2.75H8.25C7.97386 2.75 7.75 2.52614 7.75 2.25V1.75C7.75 1.33579 7.41421 1 7 1C6.58579 1 6.25 1.33579 6.25 1.75V2.5C6.25 2.63807 6.13807 2.75 6 2.75C3.79086 2.75 2 4.54086 2 6.75V16.75C2 18.9591 3.79086 20.75 6 20.75H18C20.2091 20.75 22 18.9591 22 16.75V6.75C22 4.54086 20.2091 2.75 18 2.75C17.8619 2.75 17.75 2.63807 17.75 2.5V1.75C17.75 1.33579 17.4142 1 17 1C16.5858 1 16.25 1.33579 16.25 1.75V2.25ZM7 5.5C6.58579 5.5 6.25 5.16421 6.25 4.75V4.5C6.25 4.36193 6.13807 4.25 6 4.25C4.61929 4.25 3.5 5.36929 3.5 6.75V7.5C3.5 7.77614 3.72386 8 4 8H20C20.2761 8 20.5 7.77614 20.5 7.5V6.75C20.5 5.36929 19.3807 4.25 18 4.25C17.8619 4.25 17.75 4.36193 17.75 4.5V4.75C17.75 5.16421 17.4142 5.5 17 5.5C16.5858 5.5 16.25 5.16421 16.25 4.75C16.25 4.47386 16.0261 4.25 15.75 4.25H8.25C7.97386 4.25 7.75 4.47386 7.75 4.75C7.75 5.16421 7.41421 5.5 7 5.5ZM20 9.5C20.2761 9.5 20.5 9.72386 20.5 10V16.75C20.5 18.1307 19.3807 19.25 18 19.25H6C4.61929 19.25 3.5 18.1307 3.5 16.75V10C3.5 9.72386 3.72386 9.5 4 9.5H20Z" fill="#939BA1"/>
<path d="M5.8 11.75C5.8 11.1977 6.24772 10.75 6.8 10.75H7.3C7.85228 10.75 8.3 11.1977 8.3 11.75V12.25C8.3 12.8023 7.85228 13.25 7.3 13.25H6.8C6.24772 13.25 5.8 12.8023 5.8 12.25V11.75Z" fill="#939BA1"/>
<path d="M5.8 16.25C5.8 15.6977 6.24772 15.25 6.8 15.25H7.3C7.85228 15.25 8.3 15.6977 8.3 16.25V16.75C8.3 17.3023 7.85228 17.75 7.3 17.75H6.8C6.24772 17.75 5.8 17.3023 5.8 16.75V16.25Z" fill="#939BA1"/>
<path d="M10.75 11.75C10.75 11.1977 11.1977 10.75 11.75 10.75H12.25C12.8023 10.75 13.25 11.1977 13.25 11.75V12.25C13.25 12.8023 12.8023 13.25 12.25 13.25H11.75C11.1977 13.25 10.75 12.8023 10.75 12.25V11.75Z" fill="#939BA1"/>
<path d="M10.75 16.25C10.75 15.6977 11.1977 15.25 11.75 15.25H12.25C12.8023 15.25 13.25 15.6977 13.25 16.25V16.75C13.25 17.3023 12.8023 17.75 12.25 17.75H11.75C11.1977 17.75 10.75 17.3023 10.75 16.75V16.25Z" fill="#939BA1"/>
<path d="M15.7 11.75C15.7 11.1977 16.1477 10.75 16.7 10.75H17.2C17.7523 10.75 18.2 11.1977 18.2 11.75V12.25C18.2 12.8023 17.7523 13.25 17.2 13.25H16.7C16.1477 13.25 15.7 12.8023 15.7 12.25V11.75Z" fill="#939BA1"/>
<path d="M15.7 16.25C15.7 15.6977 16.1477 15.25 16.7 15.25H17.2C17.7523 15.25 18.2 15.6977 18.2 16.25V16.75C18.2 17.3023 17.7523 17.75 17.2 17.75H16.7C16.1477 17.75 15.7 17.3023 15.7 16.75V16.25Z" fill="#939BA1"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB