noctalia-shell/Widgets/NCollapsible.qml
Ly-sec fa838ecdb1 Cleaned up ColorSchemeTab, added program checks, added firefox template
Matugen: added firefox (pywalfox) template
SidePanelToggle: use ProgramCheckerService for gpu-screen-recorder
ColorSchemeTab: use NCollapsible for matugen templates, use
ProgramCheckerService to detect available programs (for matugen
templates)
NCollapsible: create collapsible category
2025-09-15 07:44:31 +02:00

193 lines
4.9 KiB
QML

import QtQuick
import QtQuick.Layouts
import qs.Commons
ColumnLayout {
id: root
property string label: ""
property string description: ""
property bool expanded: false
property bool defaultExpanded: false
property real contentSpacing: Style.marginM * scaling
signal toggled(bool expanded)
Layout.fillWidth: true
spacing: 0
// Default property to accept children
default property alias content: contentLayout.children
// Header with clickable area
Rectangle {
id: headerContainer
Layout.fillWidth: true
Layout.preferredHeight: headerContent.implicitHeight + (Style.marginL * scaling * 2)
// Material 3 style background
color: root.expanded ? Color.mSecondary : Color.mSurfaceVariant
radius: Style.radiusL * scaling
// Subtle border
border.color: root.expanded ? Color.mOnSecondary : Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
// Smooth color transitions
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
MouseArea {
id: headerArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
root.expanded = !root.expanded
root.toggled(root.expanded)
}
// Hover effect overlay
Rectangle {
anchors.fill: parent
color: headerArea.containsMouse ? Color.mOnSurface : Color.transparent
opacity: headerArea.containsMouse ? 0.08 : 0
radius: headerContainer.radius // Reference the container's radius directly
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
}
RowLayout {
id: headerContent
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
// Expand/collapse icon with rotation animation
NIcon {
id: chevronIcon
icon: "chevron-right"
font.pointSize: Style.fontSizeL * scaling
color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignVCenter
rotation: root.expanded ? 90 : 0
Behavior on rotation {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
// Header text content - properly contained
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: Style.marginXXS * scaling
NText {
text: root.label
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightSemiBold
color: root.expanded ? Color.mOnSecondary : Color.mOnSurface
Layout.fillWidth: true
wrapMode: Text.WordWrap
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
NText {
text: root.description
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightRegular
color: root.expanded ? Color.mOnSecondary : Color.mOnSurfaceVariant
Layout.fillWidth: true
wrapMode: Text.WordWrap
visible: root.description !== ""
opacity: 0.87
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
}
}
}
// Collapsible content with Material 3 styling
Rectangle {
id: contentContainer
Layout.fillWidth: true
Layout.topMargin: Style.marginS * scaling
visible: root.expanded
color: Color.mSurface
radius: Style.radiusL * scaling
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
// Dynamic height based on content
Layout.preferredHeight: visible ? contentLayout.implicitHeight + (Style.marginL * scaling * 2) : 0
// Smooth height animation
Behavior on Layout.preferredHeight {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
// Content layout
ColumnLayout {
id: contentLayout
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: root.contentSpacing
// Clip content during animation
clip: true
}
// Fade in animation for content
opacity: root.expanded ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
}
// Initialize expanded state
Component.onCompleted: {
root.expanded = root.defaultExpanded
}
}