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" title: "StatusBlockProgressBar"
section: "Components" section: "Components"
} }
ListElement {
title: "StatusDatePicker"
section: "Components"
}
ListElement { ListElement {
title: "StatusDateRangePicker" title: "StatusDateRangePicker"
section: "Components" 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() toTimestamp: Date.now()
supportedStartYear: 1900 supportedStartYear: 1900
onNewRangeSet: { onNewRangeSet: {
console.log(" from timeStamp = ",fromTimestamp) console.warn(" from timeStamp = ", new Date(fromTimestamp).toISOString())
console.log(" to timeStamp = ",toTimestamp) console.warn(" to timeStamp = ", new Date(toTimestamp).toISOString())
} }
} }

View File

@ -1,6 +1,6 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import Qt.labs.calendar 1.0 import Qt.labs.calendar 1.0
@ -24,7 +24,7 @@ StatusComboBox {
id: root id: root
/*! /*!
\qmlproperty string StatusDatePicker::dateFormat \qmlproperty int StatusDatePicker::dateFormat
This property specifies how the selected date will be displayed to the user. This property specifies how the selected date will be displayed to the user.
Either Locale.ShortFormat (default) or Locale.LongFormat Either Locale.ShortFormat (default) or Locale.LongFormat
@ -34,152 +34,256 @@ StatusComboBox {
/*! /*!
\qmlproperty date StatusDatePicker::selectedDate \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 \sa QtQml.Date
*/ */
property alias selectedDate: d.selectedDate 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 { QtObject {
id: d id: d
property date selectedDate: new Date() 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.delegate: null
control.displayText: LocaleUtils.formatDate(root.selectedDate, root.dateFormat) control.displayText: d.isTodaySelected && root.customTodayText ? root.customTodayText
control.popup.horizontalPadding: 8 : LocaleUtils.formatDate(d.selectedDate, root.dateFormat)
control.popup.horizontalPadding: 16
control.popup.verticalPadding: 16
control.popup.width: 340
control.popup.onAboutToShow: { control.popup.onAboutToShow: {
// always open the popup showing the last (currently) selected date // always open the popup showing the last (currently) selected date
grid.year = d.selectedDate.getFullYear() d.year = d.selectedDate.getFullYear()
grid.month = d.selectedDate.getMonth() d.month = d.selectedDate.getMonth()
} }
control.popup.contentItem: Item { control.popup.contentItem: Item {
property alias grid: grid LayoutMirroring.enabled: control.locale.textDirection === Qt.RightToLeft
GridLayout { 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 anchors.fill: parent
columns: 2 spacing: 12
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.columnSpan: 2 Layout.preferredHeight: 44
spacing: 0 spacing: 0
StatusFlatButton { StatusFlatButton {
leftPadding: 8 Layout.fillHeight: true
rightPadding: 8 Layout.preferredWidth: height
text: "<<" icon.name: LayoutMirroring.enabled ? "next" : "previous"
onClicked: grid.year = grid.year - 1 icon.color: Theme.palette.directColor1
StatusToolTip { hoverColor: Theme.palette.statusMenu.hoverBackgroundColor
text: qsTr("Previous year")
visible: parent.hovered
}
}
StatusFlatButton {
leftPadding: 8
rightPadding: 8
text: "<"
onClicked: { onClicked: {
// switch to previous month (and optionally prev year in January) // switch to previous month/year (and optionally prev year in January)
const date = new Date(grid.year, grid.month) const date = new Date(d.year, d.month)
if (d.selectingMonth)
date.setFullYear(date.getFullYear() - 1)
else
date.setMonth(date.getMonth() - 1) date.setMonth(date.getMonth() - 1)
grid.month = date.getMonth() d.month = date.getMonth()
grid.year = date.getFullYear() d.year = date.getFullYear()
} }
StatusToolTip { StatusToolTip {
text: qsTr("Previous month") text: d.selectingMonth ? qsTr("Previous year") : qsTr("Previous month")
visible: parent.hovered visible: parent.hovered
} }
} }
Item { Layout.fillWidth: true }
StatusFlatButton { StatusFlatButton {
Layout.fillWidth: true Layout.fillHeight: true
text: grid.title text: d.selectingMonth ? d.year : "%1 %2".arg(control.locale.standaloneMonthName(d.month)).arg(d.year)
font.pixelSize: 18
font.weight: Font.Medium font.weight: Font.Medium
textColor: Theme.palette.directColor1
hoverColor: Theme.palette.statusMenu.hoverBackgroundColor
borderColor: hoverColor
onClicked: { onClicked: {
const date = new Date() if (d.selectingMonth) {
grid.month = date.getMonth() const thisYear = d.today.getFullYear()
grid.year = date.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 { StatusToolTip {
text: qsTr("Show current month") text: qsTr("Select year/month")
visible: parent.hovered visible: parent.hovered
} }
} }
Item { Layout.fillWidth: true }
StatusFlatButton { StatusFlatButton {
leftPadding: 8 Layout.fillHeight: true
rightPadding: 8 Layout.preferredWidth: height
text: ">" icon.name: LayoutMirroring.enabled ? "previous" : "next"
icon.color: Theme.palette.directColor1
hoverColor: Theme.palette.statusMenu.hoverBackgroundColor
onClicked: { onClicked: {
// switch to next month (and optionally next year in December) // switch to next month/year (and optionally next year in December)
const date = new Date(grid.year, grid.month) const date = new Date(d.year, d.month)
if (d.selectingMonth)
date.setFullYear(date.getFullYear() + 1)
else
date.setMonth(date.getMonth() + 1) date.setMonth(date.getMonth() + 1)
grid.month = date.getMonth() d.month = date.getMonth()
grid.year = date.getFullYear() d.year = date.getFullYear()
} }
StatusToolTip { StatusToolTip {
text: qsTr("Next month") text: d.selectingMonth ? qsTr("Next year") : qsTr("Next month")
visible: parent.hovered
}
}
StatusFlatButton {
leftPadding: 8
rightPadding: 8
text: ">>"
onClicked: grid.year = grid.year + 1
StatusToolTip {
text: qsTr("Next year")
visible: parent.hovered visible: parent.hovered
} }
} }
} }
DayOfWeekRow { StackView {
Layout.row: 1 id: stackView
Layout.column: 1
Layout.fillWidth: true 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 { delegate: StatusBaseText {
text: model.shortName text: model.shortName
font.weight: Font.Light font.weight: Font.DemiBold
color: Theme.palette.directColor3
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
locale: control.locale
} }
WeekNumberColumn { WeekNumberColumn {
id: weekColumn
month: grid.month
year: grid.year
Layout.fillHeight: true Layout.fillHeight: true
month: d.month
year: d.year
visible: root.weekNumbersVisible
spacing: 2
background: Rectangle {
color: Theme.palette.baseColor4
radius: 8
}
delegate: StatusBaseText { delegate: StatusBaseText {
text: model.weekNumber
font.weight: Font.Light
color: Theme.palette.directColor3 color: Theme.palette.directColor3
text: model.weekNumber
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
locale: control.locale
} }
MonthGrid { MonthGrid {
id: grid
month: d.selectedDate.getMonth()
year: d.selectedDate.getFullYear()
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true month: d.month
year: d.year
spacing: 2
delegate: StatusFlatButton { delegate: StatusFlatButton {
readonly property bool selected: d.selectedDate.getFullYear() === model.year && readonly property bool selected: d.selectedDate.getFullYear() === model.year &&
d.selectedDate.getMonth() === model.month && d.selectedDate.getMonth() === model.month &&
d.selectedDate.getDate() === model.day d.selectedDate.getDate() === model.day
horizontalPadding: 4 radius: width/2
opacity: model.month === grid.month ? 1 : 0.5 width: 40
height: 40
horizontalPadding: 0
verticalPadding: 0
text: model.day text: model.day
textColor: selected ? Theme.palette.primaryColor1 : Theme.palette.directColor1 normalColor: selected ? Theme.palette.primaryColor1 : "transparent"
font.bold: selected || model.today textColor: selected && !hovered ? "white" : model.month === d.month ? Theme.palette.directColor1
: Theme.palette.baseColor1
borderColor: model.today && !selected ? Theme.palette.directColor9 : "transparent"
onClicked: { onClicked: {
d.selectedDate = model.date d.selectedDate = model.today ? new Date() : model.date
root.control.popup.close() 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 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import QtQuick.Templates 2.14 as T
Item { Item {
id: root id: root
@ -25,6 +22,7 @@ Item {
property alias validationError: validationErrorItem.text property alias validationError: validationErrorItem.text
property string popupContentItemObjectName: "" property string popupContentItemObjectName: ""
property string indicatorIcon: "chevron-down"
property int size: StatusComboBox.Size.Large property int size: StatusComboBox.Size.Large
property int type: StatusComboBox.Type.Primary property int type: StatusComboBox.Type.Primary
@ -74,16 +72,14 @@ Item {
spacing: 16 spacing: 16
background: Rectangle { background: Rectangle {
implicitHeight: 24 + comboBox.topPadding + comboBox.bottomPadding
implicitWidth: 448
color: root.type === StatusComboBox.Type.Secondary ? "transparent" : Theme.palette.baseColor2 color: root.type === StatusComboBox.Type.Secondary ? "transparent" : Theme.palette.baseColor2
radius: 8 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: { border.color: {
if (!!root.validationError) if (!!root.validationError)
return Theme.palette.dangerColor1 return Theme.palette.dangerColor1
if (comboBox.activeFocus) if (comboBox.visualFocus || comboBox.popup.opened)
return Theme.palette.primaryColor1 return Theme.palette.primaryColor1
if (comboBox.hovered) if (comboBox.hovered)
@ -116,8 +112,16 @@ Item {
y: comboBox.topPadding + (comboBox.availableHeight - height) / 2 y: comboBox.topPadding + (comboBox.availableHeight - height) / 2
width: root.size === StatusComboBox.Size.Large ? 24 : 16 width: root.size === StatusComboBox.Size.Large ? 24 : 16
height: width height: width
icon: "chevron-down" icon: root.indicatorIcon
color: Theme.palette.baseColor1 color: {
if (comboBox.visualFocus || comboBox.popup.opened)
return Theme.palette.primaryColor1
if (comboBox.hovered)
return Theme.palette.primaryColor2
return Theme.palette.baseColor1
}
} }
popup: Popup { popup: Popup {
@ -133,7 +137,6 @@ Item {
verticalPadding: 8 verticalPadding: 8
background: Rectangle { background: Rectangle {
id: backgroundItem
color: Theme.palette.statusSelect.menuItemBackgroundColor color: Theme.palette.statusSelect.menuItemBackgroundColor
radius: 8 radius: 8
border.color: Theme.palette.baseColor2 border.color: Theme.palette.baseColor2

View File

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