pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import ZShell.Config import qs.Components import qs.Services import qs.Daemons CustomRect { id: root readonly property CustomText body: (expandedContent.item as ExpandedBody)?.body ?? null required property bool expanded required property NotifServer.Notif modelData readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Tokens.padding.medium * 2 : summaryHeightMetrics.height required property Props props required property PersistentProperties visibilities color: { const c = root.modelData?.urgency === "critical" ? Colors.palette.m3secondaryContainer : Colors.layer(Colors.palette.m3surfaceContainerHigh, 2); return expanded ? c : Qt.alpha(c, 0); } implicitHeight: nonAnimHeight radius: Tokens.rounding.large - Tokens.padding.small state: expanded ? "expanded" : "" Behavior on implicitHeight { Anim { } } states: State { name: "expanded" PropertyChanges { compactBody.anchors.margins: Tokens.padding.medium dummySummary.anchors.margins: Tokens.padding.medium expandedContent.anchors.margins: Tokens.padding.medium summary.anchors.margins: Tokens.padding.medium summary.maximumLineCount: Number.MAX_SAFE_INTEGER summary.width: root.width - Tokens.padding.medium * 2 - timeStr.implicitWidth - Tokens.spacing.small timeStr.anchors.margins: Tokens.padding.medium } } transitions: Transition { Anim { properties: "margins,width,maximumLineCount" } } TextMetrics { id: summaryHeightMetrics font: summary.font text: " " // Use this height to prevent weird characters from changing the line height } CustomText { id: summary anchors.left: parent.left anchors.top: parent.top color: root.modelData?.urgency === "critical" ? Colors.palette.m3onSecondaryContainer : Colors.palette.m3onSurface elide: Text.ElideRight maximumLineCount: 1 text: root.modelData?.summary ?? "" width: parent.width wrapMode: Text.WordWrap } CustomText { id: dummySummary anchors.left: parent.left anchors.top: parent.top text: root.modelData?.summary ?? "" visible: false } WrappedLoader { id: compactBody anchors.left: dummySummary.right anchors.leftMargin: Tokens.spacing.small anchors.right: parent.right anchors.top: parent.top shouldBeActive: !root.expanded sourceComponent: CustomText { color: root.modelData?.urgency === "critical" ? Colors.palette.m3secondary : Colors.palette.m3outline elide: Text.ElideRight text: String(root.modelData?.body ?? "").replace(/\n/g, " ") } } WrappedLoader { id: timeStr anchors.right: parent.right anchors.top: parent.top shouldBeActive: root.expanded sourceComponent: CustomText { animate: true color: Colors.palette.m3outline text: root.modelData?.timeStr ?? "" } } WrappedLoader { id: expandedContent anchors.left: parent.left anchors.right: parent.right anchors.top: summary.bottom anchors.topMargin: Tokens.spacing.extraSmall shouldBeActive: root.expanded sourceComponent: ExpandedBody { } } component ExpandedBody: ColumnLayout { readonly property alias body: bodyText spacing: Tokens.spacing.medium CustomText { id: bodyText Layout.fillWidth: true color: root.modelData?.urgency === "critical" ? Colors.palette.m3secondary : Colors.palette.m3onSurface text: String(root.modelData?.body ?? "").replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body given") textFormat: Text.MarkdownText wrapMode: Text.WordWrap onLinkActivated: link => { Qt.openUrlExternally(link); root.visibilities.sidebar = false; } } NotifActionList { notif: root.modelData } } component WrappedLoader: Loader { id: comp required property bool shouldBeActive active: false opacity: 0 // Makes the loader load on the same frame shouldBeActive becomes true, which ensures size is set states: State { name: "active" when: comp.shouldBeActive PropertyChanges { comp.active: true comp.opacity: 1 } } transitions: [ Transition { from: "" to: "active" SequentialAnimation { PropertyAction { property: "active" } Anim { property: "opacity" type: Anim.DefaultEffects } } }, Transition { from: "active" to: "" SequentialAnimation { Anim { property: "opacity" type: Anim.DefaultEffects } PropertyAction { property: "active" } } } ] } }