@defra/docusaurus-theme-govuk 0.0.16-alpha → 0.0.17-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/docusaurus-theme-govuk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17-alpha",
|
|
4
4
|
"description": "A Docusaurus theme implementing the GOV.UK Design System for consistent, accessible documentation sites",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
package/src/css/components.scss
CHANGED
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
line-height: 1.6;
|
|
58
58
|
font-family: Menlo, Consolas, 'Courier New', monospace;
|
|
59
59
|
position: relative;
|
|
60
|
-
border: 1px solid #
|
|
60
|
+
border: 1px solid #cecece;
|
|
61
|
+
background-color: #f4f8fb;
|
|
61
62
|
margin: 0;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -134,7 +135,7 @@
|
|
|
134
135
|
.app-layout-sidebar__nav .not-govuk-navigation-menu__list__subitems {
|
|
135
136
|
|
|
136
137
|
.not-govuk-navigation-menu__list__item {
|
|
137
|
-
padding: 5px 0
|
|
138
|
+
padding: 5px 0;
|
|
138
139
|
margin-bottom: 5px;
|
|
139
140
|
border-left-width: 0px;
|
|
140
141
|
border-left-style: solid;
|
|
@@ -145,7 +146,8 @@
|
|
|
145
146
|
|
|
146
147
|
@media (min-width: 48.125em) {
|
|
147
148
|
border-left: 4px solid #1d70b8;
|
|
148
|
-
padding-left:
|
|
149
|
+
padding-left: 11px;
|
|
150
|
+
margin-left: -15px;
|
|
149
151
|
font-weight: bold;
|
|
150
152
|
|
|
151
153
|
.not-govuk-navigation-menu__list__link {
|
|
@@ -162,6 +164,36 @@
|
|
|
162
164
|
padding-left: 0;
|
|
163
165
|
}
|
|
164
166
|
|
|
167
|
+
// API definition list (Type, Default, etc.)
|
|
168
|
+
.app-definition-list {
|
|
169
|
+
margin: 0 0 govuk-spacing(4);
|
|
170
|
+
padding: 0;
|
|
171
|
+
|
|
172
|
+
&__item {
|
|
173
|
+
display: flex;
|
|
174
|
+
gap: govuk-spacing(2);
|
|
175
|
+
align-items: baseline;
|
|
176
|
+
margin-bottom: govuk-spacing(1);
|
|
177
|
+
|
|
178
|
+
dt {
|
|
179
|
+
font-weight: 700;
|
|
180
|
+
flex-shrink: 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
dd {
|
|
184
|
+
margin: 0;
|
|
185
|
+
|
|
186
|
+
code {
|
|
187
|
+
color: #484949;
|
|
188
|
+
font-size: 0.85em;
|
|
189
|
+
background-color: #f4f8fb;
|
|
190
|
+
padding: 1px 4px;
|
|
191
|
+
border-radius: 2px;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
165
197
|
// Sidebar nav section headings and mobile toggle
|
|
166
198
|
// Desktop: headings are non-link spans, visually distinct from link items
|
|
167
199
|
.not-govuk-navigation-menu__list__heading {
|
package/src/css/prose-scope.scss
CHANGED
|
@@ -20,8 +20,21 @@
|
|
|
20
20
|
@extend %govuk-heading-s;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Extra top padding on headings that follow a code block,
|
|
24
|
+
// matching the spacing govuk-frontend adds after paragraphs and lists
|
|
25
|
+
@media (min-width: 40.0625em) {
|
|
26
|
+
.app-code-block + h2:not(.app-no-prose *),
|
|
27
|
+
.app-code-block + h3:not(.app-no-prose *),
|
|
28
|
+
.app-code-block + h4:not(.app-no-prose *),
|
|
29
|
+
.app-code-block + h5:not(.app-no-prose *),
|
|
30
|
+
.app-code-block + h6:not(.app-no-prose *) {
|
|
31
|
+
padding-top: 20px;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
// Body text
|
|
24
|
-
p:not(.app-no-prose *)
|
|
36
|
+
p:not(.app-no-prose *),
|
|
37
|
+
.app-definition-list:not(.app-no-prose *) {
|
|
25
38
|
@extend %govuk-body-m;
|
|
26
39
|
}
|
|
27
40
|
|
|
@@ -55,5 +68,67 @@
|
|
|
55
68
|
@extend %govuk-section-break;
|
|
56
69
|
@extend %govuk-section-break--visible;
|
|
57
70
|
@extend %govuk-section-break--xl;
|
|
71
|
+
border-color: #cecece;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Inline code styling (paragraphs, list items, headings etc — but not inside pre blocks)
|
|
75
|
+
code:not(pre *):not(h1 *):not(h2 *):not(h3 *):not(h4 *):not(h5 *):not(h6 *):not(.app-no-prose *) {
|
|
76
|
+
color: #484949;
|
|
77
|
+
font-size: 0.85em;
|
|
78
|
+
background-color: #f4f8fb;
|
|
79
|
+
padding: 1px 4px;
|
|
80
|
+
border-radius: 2px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Lighter table borders
|
|
84
|
+
.govuk-table__header,
|
|
85
|
+
.govuk-table__cell {
|
|
86
|
+
border-bottom-color: #cecece;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Remove the table's last row border when a section break immediately follows,
|
|
90
|
+
// so the two rules don't double up visually.
|
|
91
|
+
// Border is on td/th cells, not tr, so target those directly.
|
|
92
|
+
table:has(+ hr) tr:last-child th,
|
|
93
|
+
table:has(+ hr) tr:last-child td {
|
|
94
|
+
border-bottom: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Remove bottom margin from the last paragraph inside inset and warning text
|
|
98
|
+
.govuk-inset-text,
|
|
99
|
+
.govuk-warning-text {
|
|
100
|
+
p:last-child:not(.app-no-prose *) {
|
|
101
|
+
margin-bottom: 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Code block copy button
|
|
106
|
+
.app-code-block__copy {
|
|
107
|
+
font-size: 0.9rem;
|
|
108
|
+
min-width: 70px;
|
|
109
|
+
padding: 3px 10px;
|
|
110
|
+
border: 1px solid #1a65a6;
|
|
111
|
+
color: #1a65a6;
|
|
112
|
+
box-shadow: 0 2px 0 0 #1a65a6;
|
|
113
|
+
background-color: #fff;
|
|
114
|
+
text-align: center;
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.app-code-block__copy:active {
|
|
120
|
+
border: 2px solid var(--govuk-focus-colour, #ffdd00);
|
|
121
|
+
padding: 2px 10px;
|
|
122
|
+
outline: 2px solid rgba(0, 0, 0, 0);
|
|
123
|
+
box-shadow: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.app-code-block__copy:focus:not(:hover) {
|
|
127
|
+
border: 2px solid var(--govuk-focus-colour, #ffdd00);
|
|
128
|
+
padding: 2px 10px;
|
|
129
|
+
outline: 2px solid rgba(0, 0, 0, 0);
|
|
130
|
+
color: var(--govuk-focus-text-colour, #0b0c0c);
|
|
131
|
+
background-color: var(--govuk-focus-colour, #ffdd00);
|
|
132
|
+
box-shadow: 0 2px 0 0 var(--govuk-focus-text-colour, #0b0c0c);
|
|
58
133
|
}
|
|
59
134
|
}
|
package/src/css/theme.scss
CHANGED
|
@@ -21,6 +21,32 @@ body {
|
|
|
21
21
|
font-family: Helvetica, Arial, sans-serif;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/* Wider page width — overrides the 960px default from govuk-frontend.
|
|
25
|
+
* !important is needed because @not-govuk/width-container injects its CSS
|
|
26
|
+
* via the JS bundle, which loads after static stylesheets and wins the cascade.
|
|
27
|
+
*
|
|
28
|
+
* The package's margin: auto rule fires at 1020px (960 + 30 + 30), which is too
|
|
29
|
+
* early for our wider container. We restore the 30px gutters from 1020px and only
|
|
30
|
+
* allow auto-centering once the viewport is wide enough to actually cap at 1100px
|
|
31
|
+
* (1100 + 30 + 30 = 1160px). */
|
|
32
|
+
.govuk-width-container {
|
|
33
|
+
max-width: 1100px !important;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@media (min-width: 1020px) {
|
|
37
|
+
.govuk-width-container {
|
|
38
|
+
margin-right: 30px !important;
|
|
39
|
+
margin-left: 30px !important;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@media (min-width: 1160px) {
|
|
44
|
+
.govuk-width-container {
|
|
45
|
+
margin-right: auto !important;
|
|
46
|
+
margin-left: auto !important;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
/*
|
|
25
51
|
* Sticky footer: Docusaurus inserts a #__docusaurus wrapper between body
|
|
26
52
|
* and our layout, breaking GOV.UK Frontend's flex sticky-footer chain.
|
|
@@ -28,7 +28,7 @@ export default function CodeBlock({children, className: classNameProp, title}) {
|
|
|
28
28
|
</div>
|
|
29
29
|
)}
|
|
30
30
|
<Highlight theme={themes.github} code={codeString} language={language}>
|
|
31
|
-
{({style, tokens, getLineProps, getTokenProps}) => (
|
|
31
|
+
{({style: {backgroundColor: _bg, ...style}, tokens, getLineProps, getTokenProps}) => (
|
|
32
32
|
<pre className="app-code-block__pre" style={style}>
|
|
33
33
|
<button
|
|
34
34
|
type="button"
|
|
@@ -20,6 +20,61 @@ const MDXComponents = {
|
|
|
20
20
|
}
|
|
21
21
|
return <pre {...props} />;
|
|
22
22
|
},
|
|
23
|
+
// Paragraphs: detect API definition patterns like **Type:** `string` and render as <dl>
|
|
24
|
+
p: ({children, ...rest}) => {
|
|
25
|
+
const childArray = React.Children.toArray(children);
|
|
26
|
+
const firstChild = childArray[0];
|
|
27
|
+
|
|
28
|
+
// A definition-list paragraph starts with a <strong> whose text ends with ':'
|
|
29
|
+
const isDefinitionTerm = (node) =>
|
|
30
|
+
React.isValidElement(node) &&
|
|
31
|
+
node.type === 'strong' &&
|
|
32
|
+
typeof node.props.children === 'string' &&
|
|
33
|
+
node.props.children.trim().endsWith(':');
|
|
34
|
+
|
|
35
|
+
if (!isDefinitionTerm(firstChild)) {
|
|
36
|
+
return <p {...rest}>{children}</p>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Group children into [{term, defs}] pairs split on each <strong> label
|
|
40
|
+
const items = [];
|
|
41
|
+
let current = null;
|
|
42
|
+
for (const child of childArray) {
|
|
43
|
+
if (isDefinitionTerm(child)) {
|
|
44
|
+
if (current) items.push(current);
|
|
45
|
+
current = {term: child.props.children, defs: []};
|
|
46
|
+
} else if (current) {
|
|
47
|
+
// Skip bare whitespace/newline separators between term and value
|
|
48
|
+
if (typeof child === 'string' && child.trim() === '') continue;
|
|
49
|
+
current.defs.push(child);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (current) items.push(current);
|
|
53
|
+
|
|
54
|
+
// Render <dd> content, converting any **Required** (or similar flags) to (required)
|
|
55
|
+
const renderDefs = (defs) => defs.map((node) => {
|
|
56
|
+
if (
|
|
57
|
+
React.isValidElement(node) &&
|
|
58
|
+
node.type === 'strong' &&
|
|
59
|
+
typeof node.props.children === 'string'
|
|
60
|
+
) {
|
|
61
|
+
const label = node.props.children.trim().toLowerCase();
|
|
62
|
+
return <strong key={label}> ({label})</strong>;
|
|
63
|
+
}
|
|
64
|
+
return node;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<dl className="app-definition-list">
|
|
69
|
+
{items.map((item) => (
|
|
70
|
+
<div key={item.term} className="app-definition-list__item">
|
|
71
|
+
<dt>{item.term}</dt>
|
|
72
|
+
<dd>{renderDefs(item.defs)}</dd>
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
</dl>
|
|
76
|
+
);
|
|
77
|
+
},
|
|
23
78
|
// GOV.UK table styling
|
|
24
79
|
table: (props) => <table className="govuk-table" {...props} />,
|
|
25
80
|
thead: (props) => <thead className="govuk-table__head" {...props} />,
|
|
@@ -27,8 +82,58 @@ const MDXComponents = {
|
|
|
27
82
|
tr: (props) => <tr className="govuk-table__row" {...props} />,
|
|
28
83
|
th: (props) => <th className="govuk-table__header" {...props} />,
|
|
29
84
|
td: (props) => <td className="govuk-table__cell" {...props} />,
|
|
30
|
-
// Blockquotes rendered as GOV.UK InsetText
|
|
31
|
-
|
|
85
|
+
// Blockquotes rendered as GOV.UK InsetText, with support for GitHub-style alerts
|
|
86
|
+
// e.g. > [!NOTE], > [!WARNING], > [!TIP], > [!IMPORTANT], > [!CAUTION]
|
|
87
|
+
blockquote: ({children, ...rest}) => {
|
|
88
|
+
// Use \s* to tolerate any whitespace (newlines, \r\n, spaces) around the marker
|
|
89
|
+
const alertPattern = /^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/i;
|
|
90
|
+
const childArray = React.Children.toArray(children);
|
|
91
|
+
// Skip leading whitespace text nodes — MDX can inject them
|
|
92
|
+
const firstChildIndex = childArray.findIndex((c) => React.isValidElement(c));
|
|
93
|
+
const firstChild = childArray[firstChildIndex];
|
|
94
|
+
|
|
95
|
+
// Don't check type === 'p': when a p override is registered in MDXComponents,
|
|
96
|
+
// MDX sets the element type to the override function, not the string 'p'.
|
|
97
|
+
if (firstChild?.props) {
|
|
98
|
+
const pChildren = React.Children.toArray(firstChild.props.children);
|
|
99
|
+
|
|
100
|
+
// Remark can split "[!NOTE]" across adjacent text nodes (e.g. "[" + "!NOTE]\n...")
|
|
101
|
+
// when the paragraph also contains inline links. Merge leading text nodes before
|
|
102
|
+
// testing so the marker is always visible as a single string.
|
|
103
|
+
let mergedLeadingText = '';
|
|
104
|
+
let mergeCount = 0;
|
|
105
|
+
for (const child of pChildren) {
|
|
106
|
+
if (typeof child !== 'string') break;
|
|
107
|
+
mergedLeadingText += child;
|
|
108
|
+
mergeCount += 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const match = alertPattern.exec(mergedLeadingText);
|
|
112
|
+
if (match) {
|
|
113
|
+
const alertType = match[1].toUpperCase();
|
|
114
|
+
const remainingText = mergedLeadingText.slice(match[0].length).trimStart();
|
|
115
|
+
const newPChildren = [
|
|
116
|
+
...(remainingText ? [remainingText] : []),
|
|
117
|
+
...pChildren.slice(mergeCount),
|
|
118
|
+
].filter((c) => !(typeof c === 'string' && c.trim() === ''));
|
|
119
|
+
|
|
120
|
+
const contentParagraph = newPChildren.length > 0
|
|
121
|
+
? React.cloneElement(firstChild, {key: 'content'}, ...newPChildren)
|
|
122
|
+
: null;
|
|
123
|
+
// Use firstChildIndex + 1 so we don't re-include the original <p>
|
|
124
|
+
const allContent = [contentParagraph, ...childArray.slice(firstChildIndex + 1)].filter(Boolean);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<InsetText {...rest}>
|
|
128
|
+
<p key="title"><strong>{alertType}</strong></p>
|
|
129
|
+
{allContent}
|
|
130
|
+
</InsetText>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return <InsetText {...rest}>{children}</InsetText>;
|
|
136
|
+
},
|
|
32
137
|
h1: (props) => <Heading as="h1" {...props} />,
|
|
33
138
|
h2: (props) => <Heading as="h2" {...props} />,
|
|
34
139
|
h3: (props) => <Heading as="h3" {...props} />,
|