From 940a8f8d1c9166661ed69c42a6a3782f6d0c04cc Mon Sep 17 00:00:00 2001 From: Adam Gohs Date: Tue, 1 Sep 2026 16:43:57 -0400 Subject: [PATCH] Replace draft watermark with document status strip Swaps the fixed diagonal DRAFT watermark for a status strip in the content column on screen and a band repeated on every printed page. - DraftNotice renders both surfaces from a single status string - Strip sticks below the site header at a constant offset so it does not shift on first scroll; amber palette defined per theme - Print band repeats via thead/tfoot, the only cross-browser running header (@page margin boxes are unsupported in Chrome and Firefox), and overrides the color reset in print.css - Drops the DocItem swizzle, which existed only for the watermark - Adds the missing doc_location to the TotalRisk Verification Report entry; getDraftDocLocations() filters on that field, so no active draft doc was being marked in a production build --- src/components/DraftNotice.js | 113 ++++++++++++++++++++++++ src/css/custom.css | 1 + src/css/draft-notice.css | 141 ++++++++++++++++++++++++++++++ src/docConfig.js | 1 + src/theme/DocItem/Layout/index.js | 9 +- src/theme/DocItem/index.js | 42 --------- 6 files changed, 264 insertions(+), 43 deletions(-) create mode 100644 src/components/DraftNotice.js create mode 100644 src/css/draft-notice.css delete mode 100644 src/theme/DocItem/index.js diff --git a/src/components/DraftNotice.js b/src/components/DraftNotice.js new file mode 100644 index 000000000..547a8af56 --- /dev/null +++ b/src/components/DraftNotice.js @@ -0,0 +1,113 @@ +import { useLocation } from '@docusaurus/router'; +import latestVersions from '@site/static/versions/latestVersions.json'; +import { draftDocs } from '../draftDocs'; + +/** + * Draft status marking for documents flagged `draft: true` in docConfig.js. + * + * Two surfaces, one message: + * - DraftStatusStrip — a slim strip at the top of the content column that sticks + * below the site header, so the status stays visible regardless of scroll + * position or deep-link entry point. It is deliberately bounded by the content + * column rather than the viewport: a full-bleed bar reads as site-wide chrome, + * while a column-width bar reads as status for this document. + * - DraftPrintFrame — wraps the document body in a table whose thead/tfoot the + * browser repeats on every printed sheet, marking each physical page. + * + * The strip is hidden in print and the frame is inert on screen (display: contents), + * so each surface only renders in the medium it is meant for. + */ + +export const DRAFT_STATUS_TEXT = 'This document is pending final approval by the RMC Director.'; + +function getDocInfo(pathname) { + const stripped = pathname + .replace(/^\/RMC-Software-Documentation\/docs\//, '') + .replace(/^\/docs\//, '') + .replace(/^docs\//, ''); + const match = stripped.match(/^(.+?)\/(v\d+\.\d+(?:\.\d+)?)(?:\/|$)/); + if (!match) return null; + return { docBasePath: match[1], version: match[2] }; +} + +function isDraftPath(pathname) { + const info = getDocInfo(pathname); + if (!info) return false; + const isFlagged = draftDocs.some((base) => info.docBasePath === base || info.docBasePath.startsWith(base + '/')); + if (!isFlagged) return false; + const latest = latestVersions[info.docBasePath]; + if (!latest) return true; + return info.version === latest; +} + +/** True when the current route is the latest version of a doc flagged `draft: true`. */ +export function useIsDraftDoc() { + const location = useLocation(); + return isDraftPath(location.pathname); +} + +/** + * Slim status strip at the top of the content column. + * + * Renders nothing unless the current route is a draft doc, so it can be dropped + * into the layout unconditionally. + * + * The outer anchor is the sticky element and carries an opaque page-coloured + * band, so the gap between the header and the strip is preserved while scrolling + * without document text showing through it. + */ +export const DraftStatusStrip = () => { + const isDraft = useIsDraftDoc(); + if (!isDraft) return null; + + return ( +
+
+ {DRAFT_STATUS_TEXT} +
+
+ ); +}; + +/** + * Repeats the draft band at the top and bottom of every printed page. + * + * Browsers repeat `thead` and `tfoot` across page breaks, which is the only + * cross-browser way to get a running header/footer — `@page` margin boxes are + * unsupported in Chrome and Firefox. On screen the whole table collapses via + * `display: contents`, so it generates no boxes and leaves layout untouched. + */ +export const DraftPrintFrame = ({ children }) => { + const isDraft = useIsDraftDoc(); + if (!isDraft) return <>{children}; + + return ( + + + + + + + + + + + + + + + + +
+ +
{children}
+ +
+ ); +}; + +export default DraftStatusStrip; diff --git a/src/css/custom.css b/src/css/custom.css index 7268425f0..fd58fa49d 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -5,6 +5,7 @@ @import '../../node_modules/@usace/groundwork/dist/style.css'; @import './schema.css'; @import './print.css'; +@import './draft-notice.css'; @import './responsive.css'; @font-face { diff --git a/src/css/draft-notice.css b/src/css/draft-notice.css new file mode 100644 index 000000000..a08374c6f --- /dev/null +++ b/src/css/draft-notice.css @@ -0,0 +1,141 @@ +/* ===== Draft Status Marking ===== */ +/* Screen: a slim strip at the top of the content column that sticks below the + site header as the document scrolls. + Print: a band repeated at the top and bottom of every printed sheet. + See src/components/DraftNotice.js. */ + +/* ── Screen: document status strip ── */ +/* Bounded by the content column, not the viewport: a full-bleed bar reads as + site-wide chrome, a column-width bar reads as status for this document. */ +/* The anchor is the sticky element. It carries an opaque, page-coloured band so + the gap above the strip is preserved as the document scrolls without text + showing through it — the site header itself has a transparent background. */ +.draft-strip-anchor { + position: sticky; + top: var(--ifm-navbar-height, 0px); + z-index: 5; + + /* Holds the gap open as the document scrolls under it. Uses an explicit token + rather than --ifm-background-color, which resolves to #0000 in light mode. */ + padding-top: 1rem; + padding-bottom: 0.35rem; + /* Cancels the container's top spacing so the strip sits at its stuck position + from first paint — it locks in place instead of shifting up on first scroll. */ + margin-top: -1rem; + margin-bottom: 0.7rem; + background-color: var(--draft-strip-band); +} + +.draft-strip { + display: flex; + align-items: baseline; + justify-content: center; + flex-wrap: wrap; + gap: 0.45rem; + + padding: 0.4rem 0.9rem; + border: 1px solid var(--draft-strip-border); + border-radius: var(--ifm-global-radius); + background-color: var(--draft-strip-bg); + + color: var(--draft-strip-fg); + font-size: 0.8125rem; + line-height: 1.3; + text-align: center; +} + +/* Light mode — lighter red */ +:root { + --draft-strip-bg: #fdf3e0; + --draft-strip-border: #eed9ab; + --draft-strip-fg: #6f4a08; + --draft-strip-band: #f7f8fa; +} + +/* Dark mode — darker red */ +html[data-theme='dark'] { + --draft-strip-bg: #33280f; + --draft-strip-border: #6a5423; + --draft-strip-fg: #f2e0be; + --draft-strip-band: #1b1b1b; +} + +@media (max-width: 480px) { + .draft-strip { + font-size: 0.75rem; + padding: 0.35rem 0.7rem; + } +} + +/* ── Print frame: inert on screen ── */ +/* display: contents makes the wrapper generate no boxes, so the document lays + out exactly as it would without the table. */ +.draft-print-frame, +.draft-print-frame > thead, +.draft-print-frame > tbody, +.draft-print-frame > tfoot, +.draft-print-frame > * > tr, +.draft-print-frame > * > tr > td { + display: contents; +} + +.draft-print-frame__band { + display: none; +} + +/* ── Print ── */ +@media print { + /* The viewport strip is a screen affordance; the repeating band replaces it. */ + .draft-strip-anchor { + display: none !important; + } + + .draft-print-frame { + display: table !important; + width: 100% !important; + border-collapse: collapse !important; + } + + /* thead/tfoot repeat across page breaks — this is what marks every sheet. */ + .draft-print-frame > thead { + display: table-header-group !important; + } + + .draft-print-frame > tfoot { + display: table-footer-group !important; + } + + .draft-print-frame > tbody { + display: table-row-group !important; + } + + .draft-print-frame > * > tr { + display: table-row !important; + } + + .draft-print-frame > * > tr > td { + display: table-cell !important; + border: 0 !important; + padding: 0 !important; + } + + /* Class specificity beats the `* { color: #000 !important }` reset in print.css. */ + .draft-print-frame__band { + display: block !important; + margin: 0 0 8pt !important; + padding: 3pt 0 !important; + border-top: 1pt solid #8a5a00 !important; + border-bottom: 1pt solid #8a5a00 !important; + color: #8a5a00 !important; + font-size: 8.5pt !important; + font-weight: 700 !important; + letter-spacing: 0.06em; + text-align: center; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + .draft-print-frame__band--foot { + margin: 8pt 0 0 !important; + } +} diff --git a/src/docConfig.js b/src/docConfig.js index e5d3ace83..b159ee860 100644 --- a/src/docConfig.js +++ b/src/docConfig.js @@ -78,6 +78,7 @@ const docs = [ { category: 'desktop-applications', software: 'rmc-totalrisk', + doc_location: 'desktop-applications/rmc-totalrisk/verification-report', doc_name: 'RMC TotalRisk Verification Report', active: true, draft: true, diff --git a/src/theme/DocItem/Layout/index.js b/src/theme/DocItem/Layout/index.js index 37e38c8f6..31a9170b0 100644 --- a/src/theme/DocItem/Layout/index.js +++ b/src/theme/DocItem/Layout/index.js @@ -7,6 +7,9 @@ * - Added FloatingNav FAB + bottom-sheet drawer so users can reach * the same navigation without scrolling to the top when the * viewport is narrow (< 1080 px). + * - Added DraftStatusStrip at the top of the content column and wrapped the + * content in DraftPrintFrame, so draft documents carry a sticky status + * strip on screen and a repeating DRAFT band on every printed page. */ import React from 'react'; import clsx from 'clsx'; @@ -21,6 +24,7 @@ import DocItemContent from '@theme/DocItem/Content'; import DocBreadcrumbs from '@theme/DocBreadcrumbs'; import ContentVisibility from '@theme/ContentVisibility'; import FloatingNav, { InlineNav } from '@site/src/components/FloatingNav'; +import { DraftPrintFrame, DraftStatusStrip } from '@site/src/components/DraftNotice'; import styles from './styles.module.css'; function useDocTOC() { @@ -41,11 +45,14 @@ export default function DocItemLayout({ children }) {
+
- {children} + + {children} +
diff --git a/src/theme/DocItem/index.js b/src/theme/DocItem/index.js deleted file mode 100644 index 162334e68..000000000 --- a/src/theme/DocItem/index.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import DocItem from "@theme-original/DocItem"; -import { useLocation } from "@docusaurus/router"; -import { draftDocs } from "../../draftDocs"; -import latestVersions from "@site/static/versions/latestVersions.json"; - -function getDocInfo(pathname) { - const stripped = pathname - .replace(/^\/RMC-Software-Documentation\/docs\//, "") - .replace(/^\/docs\//, "") - .replace(/^docs\//, ""); - const match = stripped.match(/^(.+?)\/(v\d+\.\d+(?:\.\d+)?)(?:\/|$)/); - if (!match) return null; - return { docBasePath: match[1], version: match[2] }; -} - -function isDraftDoc(pathname) { - const info = getDocInfo(pathname); - if (!info) return false; - const isFlagged = draftDocs.some( - (base) => info.docBasePath === base || info.docBasePath.startsWith(base + "/") - ); - if (!isFlagged) return false; - const latest = latestVersions[info.docBasePath]; - if (!latest) return true; - return info.version === latest; -} - -export default function DocItemWrapper(props) { - const location = useLocation(); - const showWatermark = isDraftDoc(location.pathname); - return ( - <> - {showWatermark && ( -
- DRAFT -
- )} - - - ); -}