144 lines
6.9 KiB
Diff
144 lines
6.9 KiB
Diff
# Review package: 779a442..2f22df6
|
|
|
|
## Commits
|
|
2f22df6 fix: adapt shared page header to safe area
|
|
|
|
## Files changed
|
|
components/PageHeader.vue | 12 ++++++++++--
|
|
tests/shared-component-document-flow-contract.ps1 | 4 ++++
|
|
tests/shared-interaction-accessibility-contract.ps1 | 10 ++++++++++
|
|
3 files changed, 24 insertions(+), 2 deletions(-)
|
|
|
|
## Diff
|
|
diff --git a/components/PageHeader.vue b/components/PageHeader.vue
|
|
index 5ef28dd..17ab7ef 100644
|
|
--- a/components/PageHeader.vue
|
|
+++ b/components/PageHeader.vue
|
|
@@ -67,52 +67,60 @@
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
const props = defineProps({
|
|
title: { type: String, required: true },
|
|
action: { type: String, default: "" },
|
|
root: { type: Boolean, default: false },
|
|
unreadCount: { type: Number, default: 0 },
|
|
fallbackUrl: { type: String, default: "/pages/genealogy/g01-my-genealogies" },
|
|
+ customBack: { type: Boolean, default: false },
|
|
});
|
|
|
|
+const emit = defineEmits(["brand", "notice", "action", "back"]);
|
|
+
|
|
const goBack = () => {
|
|
+ if (props.customBack) {
|
|
+ emit("back");
|
|
+ return;
|
|
+ }
|
|
const stack = getCurrentPages();
|
|
if (stack.length > 1) {
|
|
uni.navigateBack();
|
|
return;
|
|
}
|
|
uni.reLaunch({ url: props.fallbackUrl });
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.page-header-slot {
|
|
- height: 104rpx;
|
|
+ height: calc(104rpx + var(--status-bar-height, 0px));
|
|
flex: none;
|
|
}
|
|
|
|
.page-header-slot--root {
|
|
height: calc(124rpx + var(--status-bar-height, 0px));
|
|
}
|
|
|
|
.page-header {
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
left: 0;
|
|
z-index: 30;
|
|
display: flex;
|
|
align-items: center;
|
|
- height: 104rpx;
|
|
+ height: calc(104rpx + var(--status-bar-height, 0px));
|
|
padding: 0 24rpx;
|
|
+ padding-top: var(--status-bar-height, 0px);
|
|
box-sizing: border-box;
|
|
background: $brand-red;
|
|
color: #fff9ed;
|
|
}
|
|
|
|
.page-header--root {
|
|
height: calc(124rpx + var(--status-bar-height, 0px));
|
|
padding-top: var(--status-bar-height, 0px);
|
|
background-color: #b52e22;
|
|
overflow: hidden;
|
|
diff --git a/tests/shared-component-document-flow-contract.ps1 b/tests/shared-component-document-flow-contract.ps1
|
|
index e7b88ae..71e6b5f 100644
|
|
--- a/tests/shared-component-document-flow-contract.ps1
|
|
+++ b/tests/shared-component-document-flow-contract.ps1
|
|
@@ -28,20 +28,24 @@ if ($header -match '(?s)\.page-header--root \.header-side,\s*\.page-header--root
|
|
throw 'PageHeader root content must not use positioning'
|
|
}
|
|
Assert-Match $header '(?s)\.header-icon-button\s*\{[^}]*display:\s*grid;[^}]*place-items:\s*center;' 'PageHeader icon button must use a grid overlay'
|
|
if ($header -match '(?s)\.header-icon-button\s*\{[^}]*position\s*:') {
|
|
throw 'PageHeader icon button must not be a positioning context'
|
|
}
|
|
Assert-Match $header '(?s)\.header-logo,\s*\.header-notice-icon,\s*\.notice-dot\s*\{[^}]*grid-area:\s*1 / 1;' 'PageHeader icon and notice dot must share one grid cell'
|
|
if ($header -match '(?s)\.notice-dot\s*\{[^}]*position\s*:') {
|
|
throw 'PageHeader notice dot must use grid alignment instead of positioning'
|
|
}
|
|
+Assert-Match $header '(?s)\.page-header-slot\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader secondary slot must reserve status bar plus 104rpx content height'
|
|
+Assert-Match $header '(?s)\.page-header\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);[^}]*padding-top:\s*var\(--status-bar-height, 0px\);' 'PageHeader secondary header must place its 104rpx content below the status bar'
|
|
+Assert-Match $header '(?s)\.page-header-slot--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root slot height must remain unchanged'
|
|
+Assert-Match $header '(?s)\.page-header--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root height must remain unchanged'
|
|
|
|
if ($card -match '(?im)(?<![-\w])position\s*:') {
|
|
throw 'GenealogyCard must use grid instead of positioning'
|
|
}
|
|
Assert-Match $card '(?s)\.genealogy-card\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*72rpx minmax\(0, 1fr\);[^}]*column-gap:\s*22rpx;' 'GenealogyCard must own the two-column document-flow grid'
|
|
Assert-Match $card '(?s)\.row-frame\s*\{[^}]*grid-area:\s*1 / 1 / 2 / -1;[^}]*z-index:\s*1;[^}]*width:\s*calc\(100% \+ var\(--card-padding-x\) \+ var\(--card-padding-x\)\);[^}]*height:\s*calc\(100% \+ var\(--card-padding-y\) \+ var\(--card-padding-y\)\);[^}]*margin:\s*calc\(-1 \* var\(--card-padding-y\)\) calc\(-1 \* var\(--card-padding-x\)\);' 'GenealogyCard row frame must span the padded card grid above the fixed page background'
|
|
Assert-Match $card '(?s)\.surname-seal\s*\{[^}]*display:\s*grid;[^}]*grid-row:\s*1;[^}]*z-index:\s*2;[^}]*place-items:\s*center;' 'GenealogyCard surname seal must share the frame row and stay above it'
|
|
Assert-Match $card '(?s)\.card-main\s*\{[^}]*grid-row:\s*1;[^}]*z-index:\s*2;' 'GenealogyCard content must share the frame row and stay above it'
|
|
Assert-Match $card '(?s)\.surname-seal-frame,\s*\.surname-seal-copy\s*\{[^}]*grid-area:\s*1 / 1;' 'GenealogyCard seal frame and copy must share one grid cell'
|
|
if ($card -match '(?s)\.card-name\s*\{[^}]*(?:overflow:\s*hidden|text-overflow:\s*ellipsis|white-space:\s*nowrap)') {
|
|
diff --git a/tests/shared-interaction-accessibility-contract.ps1 b/tests/shared-interaction-accessibility-contract.ps1
|
|
index 84ad70c..1059467 100644
|
|
--- a/tests/shared-interaction-accessibility-contract.ps1
|
|
+++ b/tests/shared-interaction-accessibility-contract.ps1
|
|
@@ -10,20 +10,30 @@ if ($button -match '<view\s+[^>]*class="app-button"') { throw 'AppButton must us
|
|
|
|
$header = Read-Utf8 'components/PageHeader.vue'
|
|
foreach ($pattern in @(
|
|
'(?s)<button[^>]+class="header-back"[^>]+aria-label=',
|
|
'(?s)<button[^>]+class="header-icon-button header-notice"[^>]+aria-label=',
|
|
'(?s)<button[^>]+class="header-icon-button"[^>]+aria-label='
|
|
)) {
|
|
if ($header -notmatch $pattern) { throw "PageHeader accessibility contract missing: $pattern" }
|
|
}
|
|
if (-not $header.Contains('fallbackUrl')) { throw 'PageHeader must expose a deep-link fallback route' }
|
|
+foreach ($token in @(
|
|
+ 'customBack: { type: Boolean, default: false }',
|
|
+ 'const emit = defineEmits(["brand", "notice", "action", "back"]);',
|
|
+ 'if (props.customBack) {',
|
|
+ 'emit("back");',
|
|
+ 'uni.navigateBack();',
|
|
+ 'uni.reLaunch({ url: props.fallbackUrl });'
|
|
+)) {
|
|
+ if (-not $header.Contains($token)) { throw "PageHeader back contract missing: $token" }
|
|
+}
|
|
|
|
$dialog = Read-Utf8 'components/AppDialog.vue'
|
|
foreach ($token in @('role="dialog"', 'aria-modal="true"', 'tabindex="-1"', '@keydown.esc.stop="cancel"', 'max-height: calc(100vh - 80rpx)', 'overflow-y: auto')) {
|
|
if (-not $dialog.Contains($token)) { throw "AppDialog accessibility contract missing: $token" }
|
|
}
|
|
|
|
$toast = Read-Utf8 'components/AppToast.vue'
|
|
foreach ($token in @('v-show="visible"', 'role="status"', 'aria-live="polite"', 'aria-atomic="true"')) {
|
|
if (-not $toast.Contains($token)) { throw "AppToast accessibility contract missing: $token" }
|
|
}
|