mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
themes: more detailed WCAG breakdowns in browser
This commit is contained in:
@@ -110,14 +110,22 @@ type ThemeWCAGGroup struct {
|
|||||||
WorstPair []string `json:"worstPair,omitempty"`
|
WorstPair []string `json:"worstPair,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ThemeWCAGBreakdown struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
BodyLevel string `json:"bodyLevel"`
|
||||||
|
}
|
||||||
|
|
||||||
type ThemeWCAGMode struct {
|
type ThemeWCAGMode struct {
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
MinRatio float64 `json:"minRatio"`
|
MinRatio float64 `json:"minRatio"`
|
||||||
WorstPair []string `json:"worstPair,omitempty"`
|
WorstPair []string `json:"worstPair,omitempty"`
|
||||||
Body *ThemeWCAGGroup `json:"body,omitempty"`
|
Body *ThemeWCAGGroup `json:"body,omitempty"`
|
||||||
Accent *ThemeWCAGGroup `json:"accent,omitempty"`
|
Accent *ThemeWCAGGroup `json:"accent,omitempty"`
|
||||||
NonText *ThemeWCAGGroup `json:"nonText,omitempty"`
|
NonText *ThemeWCAGGroup `json:"nonText,omitempty"`
|
||||||
Variants map[string]string `json:"variants,omitempty"`
|
Variants map[string]string `json:"variants,omitempty"`
|
||||||
|
Breakdown []ThemeWCAGBreakdown `json:"breakdown,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ThemeWCAG struct {
|
type ThemeWCAG struct {
|
||||||
|
|||||||
@@ -504,30 +504,145 @@ FloatingWindow {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: wcagBadge
|
id: wcagBadge
|
||||||
|
|
||||||
// Rated for the mode being viewed, since a theme
|
|
||||||
// often passes in one mode and not the other.
|
|
||||||
readonly property var modeReport: Theme.isLightMode ? modelData.wcag?.light : modelData.wcag?.dark
|
readonly property var modeReport: Theme.isLightMode ? modelData.wcag?.light : modelData.wcag?.dark
|
||||||
readonly property string level: modeReport?.level ?? ""
|
readonly property var rows: modeReport?.breakdown ?? []
|
||||||
readonly property string bodyLevel: modeReport?.body?.level ?? ""
|
readonly property var allRows: (modelData.wcag?.dark?.breakdown ?? []).concat(modelData.wcag?.light?.breakdown ?? [])
|
||||||
readonly property bool fullPass: level === "AA" || level === "AAA"
|
readonly property string level: wcagBadge.rollupLevel(wcagBadge.rows, "level")
|
||||||
readonly property bool bodyPass: bodyLevel === "AA" || bodyLevel === "AAA"
|
readonly property string bodyLevel: wcagBadge.rollupLevel(wcagBadge.rows, "bodyLevel")
|
||||||
|
readonly property bool fullPass: level !== ""
|
||||||
|
readonly property bool bodyPass: bodyLevel !== ""
|
||||||
|
readonly property bool partial: wcagBadge.isPartial(wcagBadge.rows, fullPass ? "level" : "bodyLevel")
|
||||||
|
|
||||||
|
function rollupLevel(rows, key) {
|
||||||
|
let hasAAA = false;
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const value = rows[i][key];
|
||||||
|
if (value === "AA")
|
||||||
|
return "AA";
|
||||||
|
if (value === "AAA")
|
||||||
|
hasAAA = true;
|
||||||
|
}
|
||||||
|
return hasAAA ? "AAA" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPartial(rows, key) {
|
||||||
|
let total = 0;
|
||||||
|
let passing = 0;
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const value = rows[i][key];
|
||||||
|
if (!value)
|
||||||
|
continue;
|
||||||
|
total++;
|
||||||
|
if (value !== "fail")
|
||||||
|
passing++;
|
||||||
|
}
|
||||||
|
return passing > 0 && passing < total;
|
||||||
|
}
|
||||||
|
|
||||||
height: 18
|
height: 18
|
||||||
width: wcagText.implicitWidth + Theme.spacingS
|
width: wcagRow.implicitWidth + Theme.spacingS
|
||||||
radius: 9
|
radius: 9
|
||||||
color: Theme.withAlpha(fullPass ? Theme.info : Theme.surfaceVariantText, 0.15)
|
color: Theme.withAlpha(fullPass ? Theme.info : Theme.surfaceVariantText, wcagArea.containsMouse ? 0.25 : 0.15)
|
||||||
border.color: Theme.withAlpha(fullPass ? Theme.info : Theme.surfaceVariantText, 0.4)
|
border.color: Theme.withAlpha(fullPass ? Theme.info : Theme.surfaceVariantText, 0.4)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
visible: fullPass || bodyPass
|
visible: fullPass || bodyPass
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
StyledText {
|
Row {
|
||||||
id: wcagText
|
id: wcagRow
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: wcagBadge.fullPass ? "WCAG " + wcagBadge.level : I18n.tr("WCAG %1 body", "contrast badge when only body text passes").arg(wcagBadge.bodyLevel)
|
spacing: 2
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: wcagBadge.fullPass ? Theme.info : Theme.surfaceVariantText
|
StyledText {
|
||||||
font.weight: Font.Medium
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: {
|
||||||
|
let label = "WCAG " + (wcagBadge.fullPass ? wcagBadge.level : wcagBadge.bodyLevel);
|
||||||
|
if (!wcagBadge.fullPass)
|
||||||
|
label += " " + I18n.tr("Body", "notification rule match field option");
|
||||||
|
if (wcagBadge.partial)
|
||||||
|
label += " (" + I18n.tr("Partial", "contrast badge suffix: only some variants pass") + ")";
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: wcagBadge.fullPass ? Theme.info : Theme.surfaceVariantText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: "info"
|
||||||
|
size: 12
|
||||||
|
visible: wcagBadge.allRows.length > 1
|
||||||
|
color: wcagBadge.fullPass ? Theme.info : Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: wcagArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: wcagBadge.allRows.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
onClicked: {
|
||||||
|
if (wcagBadge.allRows.length <= 1)
|
||||||
|
return;
|
||||||
|
wcagDetail.opened ? wcagDetail.close() : wcagDetail.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: wcagDetail
|
||||||
|
y: wcagBadge.height + Theme.spacingXS
|
||||||
|
padding: Theme.spacingS
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Theme.surfaceContainerHigh
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Column {
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Contrast by variant", "WCAG breakdown popup heading")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: wcagBadge.allRows
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 8
|
||||||
|
height: 8
|
||||||
|
radius: 4
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: modelData.level === "fail" ? Theme.error : Theme.success
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: modelData.name + " · " + modelData.mode
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: modelData.level === "fail" ? I18n.tr("below AA", "contrast level below the AA threshold") : "WCAG " + modelData.level
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: modelData.level === "fail" ? Theme.error : Theme.surfaceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user