feat: Generate link previews in StatusChatInput - introduce LinkPreviewMiniCard
Adding LinkPreviewMiniCard component based on Figma designs. This component is similar to LinkPreviewCard, but has less info and a different format. There is also some additional functionality attached to this card: close button, reload button and hovered state. The LinkPreviewMiniCard can have multiple states, based on content type and loading state. + Adding storybook page Figma design: https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=151TjdzkzI7flR4P-0
This commit is contained in:
parent
ebe102ac8a
commit
2c8ad61947
|
@ -0,0 +1,149 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
import shared.controls.chat 1.0
|
||||
import utils 1.0
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
layer.enabled: true
|
||||
layer.samples: 4
|
||||
background: Rectangle {
|
||||
color: Theme.palette.statusChatInput.secondaryBackgroundColor
|
||||
}
|
||||
|
||||
LinkPreviewMiniCard {
|
||||
id: previewMiniCard
|
||||
anchors.centerIn: parent
|
||||
titleStr: type === LinkPreviewMiniCard.Type.User ? userNameInput.text : titleInput.text
|
||||
domain: domainInput.text
|
||||
favIconUrl: faviconInput.text
|
||||
previewState: stateInput.currentIndex
|
||||
thumbnailImageUrl: externalImageInput.text
|
||||
type: previewTypeInput.currentIndex
|
||||
communityName: communityNameInput.text
|
||||
channelName: channelNameInput.text
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
SplitView.preferredWidth: 300
|
||||
SplitView.fillHeight: true
|
||||
ColumnLayout {
|
||||
spacing: 24
|
||||
ColumnLayout {
|
||||
|
||||
Label {
|
||||
text: "Preview type"
|
||||
}
|
||||
ComboBox {
|
||||
id: previewTypeInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
model: ["link", "image", "community", "channel", "user profile"]
|
||||
}
|
||||
Label {
|
||||
text: "Community name"
|
||||
}
|
||||
TextField {
|
||||
id: communityNameInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: "Socks"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Channel name"
|
||||
}
|
||||
TextField {
|
||||
id: channelNameInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: "General"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "User name"
|
||||
}
|
||||
TextField {
|
||||
id: userNameInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: "John Doe"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Title"
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: titleInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: "What Is Web3? A Decentralized Internet Via Blockchain Technology That Will Revolutionise All Sectors- Decrypt (@decryptmedia) August 31 2021"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Domain"
|
||||
}
|
||||
TextField {
|
||||
id: domainInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: "rarible.com"
|
||||
}
|
||||
Label {
|
||||
text: "Favicon URL"
|
||||
}
|
||||
TextField {
|
||||
id: faviconInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: "https://rarible.com/public/favicon.png"
|
||||
}
|
||||
Label {
|
||||
text: "External image URL"
|
||||
}
|
||||
TextField {
|
||||
id: externalImageInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
text: "https://rarible.com/public/favicon.png"
|
||||
}
|
||||
Label {
|
||||
text: "State"
|
||||
}
|
||||
ComboBox {
|
||||
id: stateInput
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
model: ["invalid", "loading", "loading failed", "loaded"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Settings {
|
||||
property alias linkPreviewMiniCardState: stateInput.currentIndex
|
||||
property alias linkPreviewMiniCardCommunityName: communityNameInput.text
|
||||
property alias linkPreviewMiniCardChannelName: channelNameInput.text
|
||||
property alias linkPreviewMiniCardTitle: titleInput.text
|
||||
property alias linkPreviewMiniCardDomain: domainInput.text
|
||||
property alias linkPreviewMiniCardFavIconUrl: faviconInput.text
|
||||
property alias linkPreviewMiniCardThumbnailImageUrl: externalImageInput.text
|
||||
property alias linkPreviewMiniCardType: previewTypeInput.currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
//Category: Controls
|
||||
|
||||
//"https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0"
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.96967 4.96967C5.67678 5.26256 5.67678 5.73744 5.96967 6.03033L8.08579 8.14645C8.28105 8.34171 8.28105 8.65829 8.08579 8.85355L5.96967 10.9697C5.67678 11.2626 5.67678 11.7374 5.96967 12.0303C6.26256 12.3232 6.73744 12.3232 7.03033 12.0303L10.0303 9.03033C10.3232 8.73744 10.3232 8.26256 10.0303 7.96967L7.03033 4.96967C6.73744 4.67678 6.26256 4.67678 5.96967 4.96967Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 538 B |
|
@ -34,6 +34,10 @@ Shape {
|
|||
property alias font: description.font
|
||||
|
||||
property int radius: Style.current.radius
|
||||
property int leftTopRadius: radius
|
||||
property int rightTopRadius: radius
|
||||
property int leftBottomRadius: radius
|
||||
property int rightBottomRadius: radius
|
||||
readonly property alias path: path
|
||||
|
||||
asynchronous: true
|
||||
|
@ -59,55 +63,47 @@ Shape {
|
|||
strokeStyle: ShapePath.DashLine
|
||||
dashPattern: [4, 4]
|
||||
|
||||
startX: root.radius
|
||||
startX: root.leftTopRadius
|
||||
startY: 0
|
||||
PathLine {
|
||||
x: root.width - root.radius
|
||||
x: root.width - root.rightTopRadius
|
||||
y: 0
|
||||
}
|
||||
PathCubic {
|
||||
control1X: root.width
|
||||
control2X: root.width
|
||||
control1Y: 0
|
||||
control2Y: 0
|
||||
PathArc {
|
||||
x: root.width
|
||||
y: root.radius
|
||||
y: root.rightTopRadius
|
||||
radiusX: root.rightTopRadius
|
||||
radiusY: root.rightTopRadius
|
||||
}
|
||||
PathLine {
|
||||
x: root.width
|
||||
y: root.height - root.radius
|
||||
y: root.height - root.rightBottomRadius
|
||||
}
|
||||
PathCubic {
|
||||
control1X: root.width
|
||||
control2X: root.width
|
||||
control1Y: root.height
|
||||
control2Y: root.height
|
||||
x: root.width - root.radius
|
||||
PathArc {
|
||||
x:root.width - root.rightBottomRadius
|
||||
y: root.height
|
||||
radiusX: root.rightBottomRadius
|
||||
radiusY: root.rightBottomRadius
|
||||
}
|
||||
PathLine {
|
||||
x: root.radius
|
||||
x: root.leftBottomRadius
|
||||
y: root.height
|
||||
}
|
||||
PathCubic {
|
||||
control1X: 0
|
||||
control2X: 0
|
||||
control1Y: root.height
|
||||
control2Y: root.height
|
||||
x: 0
|
||||
y: root.height - root.radius
|
||||
PathArc {
|
||||
x:0
|
||||
y: root.height - root.leftBottomRadius
|
||||
radiusX: root.leftBottomRadius;
|
||||
radiusY: root.leftBottomRadius
|
||||
}
|
||||
PathLine {
|
||||
x: 0
|
||||
y: root.radius
|
||||
y: root.leftTopRadius
|
||||
}
|
||||
PathCubic {
|
||||
control1X: 0
|
||||
control2X: 0
|
||||
control1Y: 0
|
||||
control2Y: 0
|
||||
x: root.radius
|
||||
PathArc {
|
||||
x:root.leftTopRadius
|
||||
y: 0
|
||||
radiusX: root.leftTopRadius
|
||||
radiusY: root.leftTopRadius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Shapes 1.5
|
||||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
import shared.controls 1.0
|
||||
|
||||
|
||||
Control {
|
||||
id: root
|
||||
|
||||
property bool leftTail: true
|
||||
property color backgroundColor: Style.current.background
|
||||
property color borderColor: Style.current.border
|
||||
property bool dashedBorder: false
|
||||
property real borderWidth: 1
|
||||
|
||||
readonly property Component clippingEffect: CalloutOpacityMask {
|
||||
|
@ -16,19 +21,14 @@ Control {
|
|||
height: parent.height
|
||||
leftTail: root.leftTail
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Style.current.border
|
||||
layer.enabled: true
|
||||
layer.effect: root.clippingEffect
|
||||
|
||||
Rectangle {
|
||||
id: clipping
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.borderWidth
|
||||
color: Style.current.background
|
||||
layer.enabled: true
|
||||
layer.effect: root.clippingEffect
|
||||
}
|
||||
background: ShapeRectangle {
|
||||
path.fillColor: root.backgroundColor
|
||||
path.strokeColor: root.borderColor
|
||||
path.strokeWidth: root.borderWidth
|
||||
path.strokeStyle: root.dashedBorder ? ShapePath.DashLine : ShapePath.SolidLine
|
||||
radius: Style.current.radius * 2
|
||||
leftBottomRadius: root.leftTail ? Style.current.radius / 2 : Style.current.radius * 2
|
||||
rightBottomRadius: root.leftTail ? Style.current.radius * 2 : Style.current.radius / 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import shared 1.0
|
||||
|
||||
import utils 1.0
|
||||
|
||||
CalloutCard {
|
||||
id: root
|
||||
|
||||
enum State {
|
||||
Invalid,
|
||||
Loading,
|
||||
LoadingFailed,
|
||||
Loaded
|
||||
}
|
||||
|
||||
enum Type {
|
||||
Link,
|
||||
Image,
|
||||
Community,
|
||||
Channel,
|
||||
User
|
||||
}
|
||||
|
||||
required property string titleStr
|
||||
required property string domain
|
||||
required property string communityName
|
||||
required property string channelName
|
||||
required property url favIconUrl
|
||||
required property url thumbnailImageUrl
|
||||
required property int previewState
|
||||
required property int type
|
||||
|
||||
readonly property bool containsMouse: mouseArea.hovered || reloadButton.hovered || closeButton.hovered
|
||||
|
||||
signal close()
|
||||
signal retry()
|
||||
signal clicked(var eventPoint)
|
||||
|
||||
implicitWidth: 260
|
||||
implicitHeight: 64
|
||||
verticalPadding: 15
|
||||
horizontalPadding: 12
|
||||
visible: true
|
||||
borderColor: Theme.palette.directColor7
|
||||
backgroundColor: root.containsMouse ? Theme.palette.directColor7 : Theme.palette.baseColor4
|
||||
|
||||
// behavior
|
||||
states: [
|
||||
State {
|
||||
name: "invalid"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.Invalid
|
||||
PropertyChanges {
|
||||
target: root
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "loading"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.Loading
|
||||
PropertyChanges { target: root; visible: true; dashedBorder: true; }
|
||||
PropertyChanges { target: loadingAnimation; visible: true; }
|
||||
PropertyChanges { target: titleText; text: qsTr("Generating preview..."); color: Theme.palette.baseColor1 }
|
||||
PropertyChanges { target: subtitleText; visible: false; }
|
||||
PropertyChanges { target: reloadButton; visible: false; }
|
||||
},
|
||||
State {
|
||||
name: "loadingFailed"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.LoadingFailed
|
||||
PropertyChanges { target: root; visible: true; dashedBorder: true; }
|
||||
PropertyChanges { target: loadingAnimation; visible: false; }
|
||||
PropertyChanges { target: titleText; text: qsTr("Failed to generate preview"); color: Theme.palette.directColor1 }
|
||||
PropertyChanges { target: subtitleText; visible: false; }
|
||||
PropertyChanges { target: reloadButton; visible: true; }
|
||||
},
|
||||
State {
|
||||
name: "loaded"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Link
|
||||
PropertyChanges {
|
||||
target: root; visible: true; dashedBorder: false; borderWidth: 0;
|
||||
backgroundColor: root.containsMouse ? Theme.palette.directColor8 : Theme.palette.indirectColor1;
|
||||
borderColor: backgroundColor;
|
||||
}
|
||||
PropertyChanges { target: loadingAnimation; visible: false; }
|
||||
PropertyChanges { target: titleText; text: root.titleStr; color: Theme.palette.directColor1 }
|
||||
PropertyChanges { target: subtitleText; visible: true; }
|
||||
PropertyChanges { target: reloadButton; visible: false; }
|
||||
PropertyChanges { target: favIcon; visible: true }
|
||||
},
|
||||
State {
|
||||
name: "loadedImage"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Image
|
||||
extend: "loaded"
|
||||
PropertyChanges { target: thumbnailImage; visible: root.thumbnailImageUrl != "" }
|
||||
PropertyChanges { target: favIcon; visible: true; name: root.domain; asset.isLetterIdenticon: true; asset.color: Theme.palette.baseColor2; }
|
||||
},
|
||||
State {
|
||||
name: "loadedCommunity"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Community
|
||||
extend: "loaded"
|
||||
PropertyChanges { target: titleText; text: root.communityName; }
|
||||
},
|
||||
State {
|
||||
name: "loadedChannel"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.Channel
|
||||
extend: "loaded"
|
||||
PropertyChanges { target: titleText; text: root.communityName; Layout.fillWidth: false; Layout.maximumWidth: Math.min(92, implicitWidth); }
|
||||
PropertyChanges { target: secondTitleText; text: root.channelName; visible: true; }
|
||||
},
|
||||
State {
|
||||
name: "loadedUser"
|
||||
when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === LinkPreviewMiniCard.Type.User
|
||||
extend: "loaded"
|
||||
PropertyChanges { target: favIcon; visible: true; name: root.titleStr; asset.isLetterIdenticon: true; asset.charactersLen: 2; asset.color: Theme.palette.miscColor9; }
|
||||
}
|
||||
]
|
||||
|
||||
contentItem: Item {
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
LoadingAnimation {
|
||||
id: loadingAnimation
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.margins: 4
|
||||
visible: false
|
||||
}
|
||||
StatusSmartIdenticon {
|
||||
id: favIcon
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.topMargin: 1
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
visible: false
|
||||
name: root.titleStr
|
||||
asset.name: root.favIconUrl
|
||||
asset.letterSize: asset.charactersLen == 1 ? 10 : 7
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 8
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
id: titleRow
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 0
|
||||
StatusBaseText {
|
||||
id: titleText
|
||||
text: root.titleStr
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
font.pixelSize: Style.current.additionalTextSize
|
||||
font.weight: Font.Medium
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
maximumLineCount: 1
|
||||
}
|
||||
StatusIcon {
|
||||
id: secondTitleIcon
|
||||
width: 16
|
||||
height: 16
|
||||
icon: "tiny/chevron-right"
|
||||
color: Theme.palette.baseColor1
|
||||
visible: secondTitleText.visible
|
||||
|
||||
}
|
||||
StatusBaseText {
|
||||
id: secondTitleText
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
font.pixelSize: Style.current.additionalTextSize
|
||||
font.weight: Font.Medium
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
maximumLineCount: 1
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
StatusBaseText {
|
||||
id: subtitleText
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
font.pixelSize: Style.current.tertiaryTextFontSize
|
||||
color: Theme.palette.baseColor1
|
||||
text: root.domain
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
StatusRoundedImage {
|
||||
id: thumbnailImage
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: 4
|
||||
implicitWidth: 34
|
||||
implicitHeight: 34
|
||||
radius: 4
|
||||
image.source: root.thumbnailImageUrl
|
||||
visible: false
|
||||
}
|
||||
StatusFlatButton {
|
||||
id: reloadButton
|
||||
icon.name: "refresh"
|
||||
size: StatusBaseButton.Size.Small
|
||||
hoverColor: Theme.palette.directColor8
|
||||
textColor: Theme.palette.directColor1
|
||||
onClicked: root.retry()
|
||||
}
|
||||
StatusFlatButton {
|
||||
id: closeButton
|
||||
icon.name: "close"
|
||||
size: StatusBaseButton.Size.Small
|
||||
hoverColor: Theme.palette.directColor8
|
||||
textColor: Theme.palette.directColor1
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: mouseArea
|
||||
target: background
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
TapHandler {
|
||||
id: tapHandler
|
||||
target: background
|
||||
onTapped: root.clicked(eventPoint)
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ CalloutCard 1.0 CalloutCard.qml
|
|||
FetchMoreMessagesButton 1.0 FetchMoreMessagesButton.qml
|
||||
GapComponent 1.0 GapComponent.qml
|
||||
LinkPreviewCard 1.0 LinkPreviewCard.qml
|
||||
LinkPreviewMiniCard 1.0 LinkPreviewMiniCard.qml
|
||||
UsernameLabel 1.0 UsernameLabel.qml
|
||||
UserProfileCard 1.0 UserProfileCard.qml
|
||||
DateGroup 1.0 DateGroup.qml
|
||||
|
|
Loading…
Reference in New Issue