feat(qml): MD3 rework batch 3 — atoms (CPanel, CSection, CBlockquote, CHighlight, CProse, CMarkdown)

CPanel: radius 16, tonal surface container, tonal primary header
CSection: 22px bold title, inline divider
CBlockquote: 4px left accent border, tonal primary background, radius 8
CHighlight: tonal color variants at 15% opacity, radius 4
CProse: 16px body, 1.6 line-height, proportional height mode
CMarkdown: updated typography to match MD3 body scale

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 04:04:31 +00:00
parent de3a3ac194
commit 41eb2d8e3e
6 changed files with 176 additions and 144 deletions

View File

@@ -3,53 +3,65 @@ import QtQuick.Layouts
import QmlComponents 1.0
/**
* CBlockquote.qml - Blockquote (mirrors _blockquote.scss)
* Styled quote block with left border
* CBlockquote.qml - Material Design 3 blockquote
* Styled quote block with primary left border and tonal background
*/
Rectangle {
id: root
property string text: ""
property string cite: ""
color: Theme.mode === "dark" ? Qt.rgba(255, 255, 255, 0.03) : Qt.rgba(0, 0, 0, 0.02)
radius: StyleVariables.radiusSm
// MD3 tonal primary background
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.04)
// MD3 rounded right side, squared left (border covers left rounding)
radius: 8
implicitWidth: parent ? parent.width : 400
implicitHeight: contentCol.implicitHeight + StyleVariables.spacingMd * 2
// Left accent border
implicitHeight: contentCol.implicitHeight + 32
// Left accent border - 4px primary with radius 2
Rectangle {
id: leftBorder
width: 4
height: parent.height
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
color: Theme.primary
radius: 2
}
ColumnLayout {
id: contentCol
anchors.fill: parent
anchors.leftMargin: StyleVariables.spacingMd + 4
anchors.rightMargin: StyleVariables.spacingMd
anchors.topMargin: StyleVariables.spacingMd
anchors.bottomMargin: StyleVariables.spacingMd
spacing: StyleVariables.spacingSm
anchors.left: leftBorder.right
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.topMargin: 16
anchors.bottomMargin: 16
spacing: 8
Text {
Layout.fillWidth: true
text: root.text
color: Theme.onSurface
font.pixelSize: StyleVariables.fontSizeMd
color: Theme.text
font.pixelSize: 16
font.italic: true
font.letterSpacing: 0.5
wrapMode: Text.Wrap
lineHeight: 1.6
lineHeightMode: Text.ProportionalHeight
}
Text {
Layout.fillWidth: true
text: "— " + root.cite
color: Theme.onSurfaceVariant
font.pixelSize: StyleVariables.fontSizeSm
color: Theme.textSecondary
font.pixelSize: 14
font.letterSpacing: 0.25
visible: root.cite !== ""
}
}

View File

@@ -2,48 +2,47 @@ import QtQuick
import QmlComponents 1.0
/**
* CHighlight.qml - Text highlighting (mirrors _highlight.scss)
* Highlight text with background color
* CHighlight.qml - Material Design 3 inline text highlight
* Highlight text with tonal background color
*/
Rectangle {
id: root
property alias text: label.text
property string variant: "default" // default, success, warning, error, info
// Color mapping
// MD3 tonal color mapping
readonly property color _bgColor: {
switch (variant) {
case "success": return Theme.successContainer
case "warning": return Theme.warningContainer
case "error": return Theme.errorContainer
case "info": return Theme.infoContainer
default: return Theme.mode === "dark"
? Qt.rgba(255, 255, 0, 0.2)
: Qt.rgba(255, 255, 0, 0.4)
case "success": return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.15)
case "warning": return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.15)
case "error": return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15)
case "info": return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.15)
default: return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15)
}
}
readonly property color _textColor: {
switch (variant) {
case "success": return Theme.success
case "warning": return Theme.warning
case "error": return Theme.error
case "info": return Theme.info
default: return Theme.onSurface
default: return Theme.text
}
}
color: _bgColor
radius: StyleVariables.radiusSm / 2
implicitWidth: label.implicitWidth + StyleVariables.spacingXs * 2
implicitHeight: label.implicitHeight + 2
radius: 4
implicitWidth: label.implicitWidth + 8
implicitHeight: label.implicitHeight + 4
Text {
id: label
anchors.centerIn: parent
color: root._textColor
font.pixelSize: StyleVariables.fontSizeSm
font.pixelSize: 14
font.letterSpacing: 0.25
}
}

View File

@@ -2,25 +2,27 @@ import QtQuick
import QmlComponents 1.0
/**
* CMarkdown.qml - Markdown text display (mirrors _markdown.scss)
* Renders markdown-formatted text using Qt's built-in support
* CMarkdown.qml - Material Design 3 markdown text display
* Renders markdown-formatted text with MD3 typography
*/
Text {
id: root
property string markdown: ""
text: markdown
textFormat: Text.MarkdownText
color: Theme.onSurface
font.pixelSize: StyleVariables.fontSizeMd
color: Theme.text
font.pixelSize: 16
font.letterSpacing: 0.5
wrapMode: Text.Wrap
lineHeight: 1.6
// Link styling
lineHeightMode: Text.ProportionalHeight
// MD3 primary link color
linkColor: Theme.primary
onLinkActivated: (link) => Qt.openUrlExternally(link)
// Cursor for links
MouseArea {
anchors.fill: parent

View File

@@ -4,22 +4,22 @@ import QtQuick.Effects
import QmlComponents 1.0
/**
* CPanel.qml - Panel component (mirrors _panel.scss)
* CPanel.qml - Material Design 3 surface container panel
* Floating panel with header, body, and footer sections
*
*
* Usage:
* CPanel {
* title: "Queue Status"
* icon: "📋"
* collapsible: true
*
*
* // Content goes in body
* Text { text: "Panel content" }
* }
*/
Rectangle {
id: root
// Public properties
property string title: ""
property string icon: ""
@@ -28,57 +28,60 @@ Rectangle {
property bool collapsible: false
property bool collapsed: false
property string footer: ""
// Content slot
default property alias content: bodyContent.data
// Signals
signal headerClicked()
// Size
implicitWidth: 280
implicitHeight: collapsed ? headerRect.height : (headerRect.height + bodyLoader.height + footerLoader.height)
// Positioning
anchors.right: position === "fixed-br" || position === "fixed-tr" ? parent.right : undefined
anchors.left: position === "fixed-bl" || position === "fixed-tl" ? parent.left : undefined
anchors.bottom: position === "fixed-br" || position === "fixed-bl" ? parent.bottom : undefined
anchors.top: position === "fixed-tr" || position === "fixed-tl" ? parent.top : undefined
anchors.margins: position !== "none" ? StyleVariables.spacingMd : 0
// Appearance
color: Theme.paper
radius: StyleVariables.radiusMd
// Shadow for elevated variant
anchors.margins: position !== "none" ? 16 : 0
// MD3 Surface container
color: Theme.mode === "dark" ? Qt.rgba(1, 1, 1, 0.08) : Qt.rgba(0.31, 0.31, 0.44, 0.10)
radius: 16
border.width: 1
border.color: Theme.mode === "dark" ? Qt.rgba(1, 1, 1, 0.12) : Qt.rgba(0, 0, 0, 0.12)
// MD3 elevation shadow for elevated variant
layer.enabled: variant === "elevated"
layer.effect: MultiEffect {
shadowEnabled: true
shadowColor: "#40000000"
shadowBlur: 0.4
shadowVerticalOffset: 6
shadowColor: "#30000000"
shadowBlur: 0.6
shadowVerticalOffset: 4
shadowHorizontalOffset: 0
}
// Smooth collapse animation
Behavior on implicitHeight {
NumberAnimation { duration: StyleVariables.transitionNormal; easing.type: Easing.OutCubic }
NumberAnimation { duration: 250; easing.type: Easing.OutCubic }
}
// Layout
ColumnLayout {
anchors.fill: parent
spacing: 0
// Header
Rectangle {
id: headerRect
Layout.fillWidth: true
Layout.preferredHeight: 40
color: Qt.darker(Theme.primary, 1.2)
Layout.preferredHeight: 48
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
radius: root.radius
visible: root.title || root.icon
// Square off bottom corners
// Square off bottom corners when not collapsed
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
@@ -87,7 +90,7 @@ Rectangle {
color: parent.color
visible: !root.collapsed
}
MouseArea {
anchors.fill: parent
cursorShape: root.collapsible ? Qt.PointingHandCursor : Qt.ArrowCursor
@@ -98,98 +101,104 @@ Rectangle {
root.headerClicked()
}
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: StyleVariables.spacingMd
anchors.rightMargin: StyleVariables.spacingMd
spacing: StyleVariables.spacingSm
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 12
// Icon
Text {
visible: root.icon
text: root.icon
font.pixelSize: StyleVariables.fontSizeMd
color: "#ffffff"
font.pixelSize: 16
color: Theme.primary
}
// Title
Text {
Layout.fillWidth: true
text: root.title
font.pixelSize: StyleVariables.fontSizeSm
font.pixelSize: 14
font.weight: Font.Medium
color: "#ffffff"
font.letterSpacing: 0.1
color: Theme.text
elide: Text.ElideRight
}
// Collapse indicator
Text {
visible: root.collapsible
text: root.collapsed ? "▼" : "▲"
font.pixelSize: 10
color: "#cccccc"
color: Theme.textSecondary
}
}
}
// Body
Loader {
id: bodyLoader
Layout.fillWidth: true
active: !root.collapsed
visible: active
sourceComponent: Rectangle {
width: bodyLoader.width
height: Math.min(bodyContent.implicitHeight, 300)
height: Math.min(bodyContent.implicitHeight + 32, 332)
color: "transparent"
clip: true
Flickable {
anchors.fill: parent
anchors.topMargin: 16
anchors.bottomMargin: 16
anchors.leftMargin: 16
anchors.rightMargin: 16
contentHeight: bodyContent.implicitHeight
clip: true
boundsBehavior: Flickable.StopAtBounds
ColumnLayout {
id: bodyContent
width: parent.width
spacing: StyleVariables.spacingSm
spacing: 8
}
ScrollBar.vertical: ScrollBar {
policy: bodyContent.implicitHeight > 300 ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
}
}
}
}
// Footer
Loader {
id: footerLoader
Layout.fillWidth: true
active: root.footer && !root.collapsed
visible: active
sourceComponent: Rectangle {
width: footerLoader.width
height: 32
height: 40
color: "transparent"
// Top border
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: Theme.divider
color: Theme.mode === "dark" ? Qt.rgba(1, 1, 1, 0.12) : Qt.rgba(0, 0, 0, 0.12)
}
Text {
anchors.centerIn: parent
text: root.footer
font.pixelSize: StyleVariables.fontSizeXs
font.pixelSize: 12
font.letterSpacing: 0.4
color: Theme.textSecondary
}
}

View File

@@ -3,19 +3,19 @@ import QtQuick.Layouts
import QmlComponents 1.0
/**
* CProse.qml - Long-form text container (mirrors _prose.scss)
* Optimized typography for reading
* CProse.qml - Material Design 3 long-form text container
* Rich text container with proper line-height and typography
*/
ColumnLayout {
id: root
property string size: "md" // sm, md, lg
property real maxWidth: 680 // Optimal reading width
default property alias content: contentItem.data
spacing: StyleVariables.spacingMd
spacing: 16
// Content container with max-width
Item {
id: contentItem
@@ -23,8 +23,8 @@ ColumnLayout {
Layout.maximumWidth: root.maxWidth
Layout.alignment: Qt.AlignHCenter
implicitHeight: childrenRect.height
// Apply prose styles to child Text elements
// Apply MD3 prose styles to child Text elements
Component.onCompleted: {
for (var i = 0; i < children.length; i++) {
var child = children[i]
@@ -34,21 +34,23 @@ ColumnLayout {
}
}
}
function applyProseStyle(textItem) {
textItem.color = Theme.onSurface
textItem.color = Theme.text
textItem.wrapMode = Text.Wrap
textItem.lineHeight = 1.7
textItem.lineHeight = 1.6
textItem.lineHeightMode = Text.ProportionalHeight
textItem.font.letterSpacing = 0.5
switch (size) {
case "sm":
textItem.font.pixelSize = StyleVariables.fontSizeSm
textItem.font.pixelSize = 14
break
case "lg":
textItem.font.pixelSize = StyleVariables.fontSizeLg
textItem.font.pixelSize = 18
break
default:
textItem.font.pixelSize = StyleVariables.fontSizeMd
textItem.font.pixelSize = 16
}
}
}

View File

@@ -3,58 +3,66 @@ import QtQuick.Layouts
import QmlComponents 1.0
/**
* CSection.qml - Content section (mirrors _section.scss)
* Groups related content with optional title
* CSection.qml - Material Design 3 content section
* Groups related content with optional title and subtitle
*/
ColumnLayout {
id: root
property string title: ""
property string subtitle: ""
property bool divider: false
property string spacing: "md" // sm, md, lg
default property alias content: contentItem.data
spacing: {
switch (root.spacing) {
case "sm": return StyleVariables.spacingSm
case "lg": return StyleVariables.spacingLg
default: return StyleVariables.spacingMd
case "sm": return 8
case "lg": return 24
default: return 16
}
}
// Header
ColumnLayout {
Layout.fillWidth: true
spacing: StyleVariables.spacingXs
spacing: 4
visible: root.title !== "" || root.subtitle !== ""
Text {
Layout.fillWidth: true
text: root.title
color: Theme.onSurface
font.pixelSize: StyleVariables.fontSizeLg
font.weight: Font.DemiBold
color: Theme.text
font.pixelSize: 22
font.weight: Font.Bold
font.letterSpacing: 0
lineHeight: 1.27
lineHeightMode: Text.ProportionalHeight
visible: root.title !== ""
}
Text {
Layout.fillWidth: true
text: root.subtitle
color: Theme.onSurfaceVariant
font.pixelSize: StyleVariables.fontSizeSm
color: Theme.textSecondary
font.pixelSize: 14
font.letterSpacing: 0.25
lineHeight: 1.43
lineHeightMode: Text.ProportionalHeight
wrapMode: Text.Wrap
visible: root.subtitle !== ""
}
}
// Divider after header
CDivider {
// Bottom divider after header
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.mode === "dark" ? Qt.rgba(1, 1, 1, 0.12) : Qt.rgba(0, 0, 0, 0.12)
visible: root.divider && root.title !== ""
}
// Content
Item {
id: contentItem