Space and indent embedded components inside lists - #3325
Conversation
Figma wraps every non-text block embedded in the body in a slot with space/8 above and below, on top of the space/16 block gap. That landed for top-level images and code blocks, but the rule that spaces body blocks uses a child combinator, so nothing inside a list item was reached and code blocks sat flush against the text above them. Tables were left out of the embedded set, and their cells referenced --space-16 and --space-24, which don't exist - the tokens are --space16 and --space24 - so cells had no padding at all. The copy button lives in a zero-height div injected in front of each code block, so the block's own top margin was pushing the block away from its button rather than away from the text above it. li:has(figure) is gone: it was spacing figures in list items with flex gap, which stacks with the margin instead of collapsing into it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Pull request environment is available at https://stoctodocspr3325.z22.web.core.windows.net. You can view the ephemeral environment status in Octopus Deploy. This environment will be automatically deprovisioned when the pull request is closed, or after 7 days of inactivity. |
|
I noticed this PR added numbers to some lists that weren't there before. Example (note the new What's going on here, was this intentional? It wasn't called out in the "bugs found along the way" in the PR description, but looking at the markdown this seems like a fix to me, these numbers probably should've been visible all along 🤔 |
|
@rosslovas @enf0rc3 Yeah, that's super weird about the list now showing Viewing source on both prod and the staging site, it's identical, both are simply <li>
<p>Give the new Private Endpoint a name and either accept or customize the generated Network Interface Name. Click “Next”.</p>
<figure><p><img src="/docs/img/octopus-cloud/images/create-private-endpoint-basics.png" alt="An example of how to fill in the basics tab while creating a private endpoint in the Azure Portal"></p></figure>
</li>a) Why didn't we previously see it? I asked claude and it says this
Using browser devtools, the other li's show a .page-content {
/* Heading gaps rely on sibling margin collapsing, which only happens in block
flow. Converting this to flex or grid makes every gap additive again -
56px above an h2 becomes 72px after a paragraph, 88px after a blockquote. */
display: flow-root;
padding-block-start: var(--space32);
li:has(figure) {
display: flex;
flex-direction: column;
gap: 1rem;
}
}Which this PR has removed. Nice fix. Approved |
#3322 moved .btn into Button.astro's scoped style block, so it only reaches markup that component renders. The copy button comes from the Shiki transformer and the language select is built by script, so neither carries the scoping attribute and both lost their styling in the rebase. They carry the geometry themselves now, the same trade #3322 made for .copy-heading-url: it can drift from <Button>, and that is worth more than making a selector as broad as .btn global. Also drops the .copy-container rules that arrived with #3325. That class went with the old copy button this branch replaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Give code blocks a header with a language and copy button NES-285. Every fenced block is now wrapped in a shell carrying the block's label, its language and a copy button, matching the CodeBlock component in the design system. A group of <details data-group> panels that are each a lone code block becomes one block whose header offers the languages in a menu, replacing the tab list. Groups holding prose as well stay tabs. Blocks over 500px collapse behind a gradient fade until they are clicked. Text after the language on the opening fence becomes the block's label. Shiki drops that meta string, so a transformer keeps it on the element. The fences that only repeated their own language there have it removed, since that would render a label saying what the header already shows. Shiki now highlights against both themes, so dark mode gets real syntax colours in place of the inverting filter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Clear the markdownlint errors in the files this branch touches The workflow lints only the files a branch changed, so touching these surfaced 93 violations that were already there. None came from this branch: the same files on main report the same errors. Most were mechanical and went through markdownlint --fix. The rest needed a decision: - The expanded-properties table in certificate-variables was missing its trailing pipes and its third column, so twelve rows were losing data. It is rebuilt with every row filled in, and `header\footer` reads header/footer. - Six fences had no language. They are `text` now, with a label saying what the service message does. - output-variables used **PowerShell**, **C#**, **Bash**, **F#** and **Python3** as headings above their fences. The section heading already names the language and the block header now shows it, so the emphasis is gone and each fence carries a label instead. - The two certificate screenshots have alt text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Render the code block shell at build time The frame, header, label and language were being built by JavaScript after the page loaded. With scripting off, and in the window before hydration, a code block was bare text on the page background: the border, radius and padding used to sit on <pre> and now sit on the wrapper that script created. A Shiki transformer emits the whole shell instead, including the copy button. The copy handler is delegated at the document level, so it finds a statically rendered button by the same selector. Shiki, and not rehype, because plugins registered through `markdown.processor` never reach .mdx pages. rehypeWbr adds 18 <wbr> elements to the kubernetes-agent permissions page and none to kustomize.mdx, which has eight matches for it. code-blocks.js drops from 431 lines to 327: the wrapping, the copy button markup and the language table all go. It keeps copying, collapsing, and folding a <details data-group> set into one block with a language menu, which merges sibling blocks and so cannot be done per-block at build time. Two tests cover the shell with scripting disabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Share one copy-button module between the heading and the code block Both had their own copy of the same fifty-five lines: the revert timer, the tooltip swap, the live region, and the delegated click. The only thing that differed was the string each one copies. copy-button.js takes a selector and a function that reads the text, so a caller is left with just that function. A button's own data-tooltip is its resting label, which keeps "Copy URL" on the heading and "Copy to clipboard" on the code block, and the two share one live region instead of one each. headers.js goes from 125 lines to 52, code-blocks.js from 327 to 256. copy-markdown.js stays as it is. It fetches the page over the network before writing, so it needs the execCommand fallback and cannot read its text synchronously, which is what keeps the clipboard write inside Safari's user activation. The heading button had no test. It has three now, covering both callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Switch the language control to a native select The menu was a <details> with a hand-built option list, and eighteen of its lines re-implemented Escape-to-close and click-away-to-close. A <select> comes with those, plus keyboard navigation, focus handling and the mobile picker. code-blocks.js drops from 256 lines to 201, and the CSS loses the popup panel along with it. The trigger still matches the design. The list it opens is the browser's, so that part no longer matches the Figma panel: worth Mandy's eye before this merges. The caret is a mask on a wrapper span, since a <select> renders no pseudo-element of its own and a background image cannot follow the theme. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Use the caret the rest of the site already uses The switcher had its own caret-down.svg. The Button component and the copy markdown menu both draw theirs from the FontAwesome glyph, so this does too and the asset goes. Rebasing also turned up a conflict git could not see. Main now sets `margin-block` on `.page-content :is(pre, figure)`, and every <pre> sits inside a code block, so the code was pushed away from its own header. The rule points at .code-block instead, which is the element that wanted the spacing, and the block drops the 1rem it was setting for itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix a switcher with nothing to switch, and a brittle transformer A <details data-group> set with one member built a select holding one option, which is a control that cannot do anything. There is one such group in the docs. The block still loses its <details>, and its summary text becomes the fixed language. The transformer assigned over root.children, which would drop anything a later Shiki version put alongside the <pre>. It replaces the <pre> in place instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Left-align the language in the switcher .btn centres its text, and the caret needs the padding to be lopsided: 5px one side, 24px the other. A short language centred in what is left of the box landed about ten pixels off the button's own centre, reading as neither centred nor aligned. The box stays as wide as the longest language, so switching causes no reflow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Give a tab stop only to code that can be opened with one The old module made every <pre> focusable so a long line could be scrolled without a mouse. Nothing scrolls: `white-space: break-spaces` wraps the code, and none of the 43 blocks sampled across four pages overflow their box. The attribute still earns its place on a collapsed block, where the code is the only thing in the body that can take focus and focus is what opens it. So it goes on there and nowhere else, which is around 1,500 fewer tab stops. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put a Show more button under a collapsed block The fade was 59px and did not start until 60% down it, so it only really acted over the last 24px and the code read as simply ending. It is 7.5rem now and fades from its own top edge. Under it sits a Show more button that becomes Show less. That is what carries the message, and it is also the keyboard route in, so the <pre> no longer needs a tab stop and the focus handler that stood in for one is gone. Opening is one way apart from that button. Collapsing when a click landed elsewhere pulled the page up by the height of the block, which moved everything under the reader and lost their place. Collapsing from the button scrolls the block back into view for the same reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Settle the Show more button, and land at the top when it closes Secondary text, space12 of padding, and no rule above it. Closing now puts the reader at the top of the block instead of near where it was. block: 'nearest' left them wherever the collapsed height happened to run out, which on a tall block is a different part of the page. Instant, because animating a jump that size only reads as lag. The block lands 140px down, which is the scroll-padding the site already sets to clear the sticky header. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Keep the collapse jump instant The autofix on the pull request swapped behavior: 'instant' for 'auto'. 'auto' defers to the CSS, and main.css sets scroll-behavior: smooth on html for anyone without a reduced-motion preference, so the jump animated again. 'instant' is CSSOM View and has been in every current browser since 2023. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Give the code block controls their own button styles #3322 moved .btn into Button.astro's scoped style block, so it only reaches markup that component renders. The copy button comes from the Shiki transformer and the language select is built by script, so neither carries the scoping attribute and both lost their styling in the rebase. They carry the geometry themselves now, the same trade #3322 made for .copy-heading-url: it can drift from <Button>, and that is worth more than making a selector as broad as .btn global. Also drops the .copy-container rules that arrived with #3325. That class went with the old copy button this branch replaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>


Implements the Embedded component design.
Figma wraps every non-text block embedded in the body — image, callout, table, code block — in a slot with
space/8above and below, on top of thespace/16block gap, and indents it with the list when it sits inside one.Indentation is 24px at list level 1 and 48px at level 2, matching the design.
Verified on the preview
Measured in the browser, light and dark:
Bugs found along the way
--space-16/--space-24/--space-12. Those don't exist — the tokens are--space16etc. — so every cell on every page with a markdown table rendered with no padding at all.li:has(figure)spaced figures in list items with flexgap, which stacks with the margin instead of collapsing into it.Checked, no change needed
The leading section (32px above the first paragraph, 56px above an h2, 12px below it, 16px between paragraphs) and the list (24px indent, no top padding on the first item, 6px between items) both already match main.
Out of scope: the code block header bar, which is #3317.
🤖 Generated with Claude Code