Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/reusables/billing/cta-ghec-cost-centers.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Cost centers are available with {% data variables.product.prodname_enterprise %}
<a href="https://github.com/account/enterprises/new?ref_product=ghec&ref_type=trial&ref_style=button&ref_plan=enterprise" target="_blank" class="btn btn-primary mt-3 mr-3 no-underline"><span>Set up a trial of {% data variables.product.prodname_ghe_cloud %}</span> {% octicon "link-external" height:16 aria-label="link-external" %}</a>
<br><a href="https://github.com/account/enterprises/new?ref_product=ghec&ref_type=trial&ref_style=button&ref_plan=enterprise" target="_blank" class="btn btn-primary mt-3 mr-3 no-underline"><span>Set up a trial of {% data variables.product.prodname_ghe_cloud %}</span> {% octicon "link-external" height:16 aria-label="link-external" %}</a>
5 changes: 4 additions & 1 deletion data/variables/secret-scanning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ pricing-calculator: 'pricing calculator'
# Secret risk assessment call to action links. If changing the links below, also update the hard-coded link in /code-security/index.md
secret-risk-assessment-cta-link: 'https://github.com/get_started?with=risk-assessment'
secret-risk-assessment-cta-text: 'Run a security risk assessment'
secret-risk-assessment-cta-product: '[<span class="btn btn-primary mt-3 mr-3 no-underline">{% data variables.secret-scanning.secret-risk-assessment-cta-text %}</span>](https://github.com/get_started?with=risk-assessment)'
# Keep this CTA as a raw <a>, matching every other button in the content. The button
# styles are all qualified `a.btn`, so the `[<span class="btn">LABEL](URL)` form puts
# the classes on an inner span and the button silently misses them.
secret-risk-assessment-cta-product: '<a href="https://github.com/get_started?with=risk-assessment" class="btn btn-primary mt-3 mr-3 no-underline"><span>{% data variables.secret-scanning.secret-risk-assessment-cta-text %}</span></a>'

# Combined to provide a secret to demonstrate push protection. Dummy secret, no access.
learner-example-secret-a: 'secret_scanning_ab85fc6f8d76'
Expand Down
78 changes: 78 additions & 0 deletions src/content-render/stylesheets/markdown-overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,81 @@
margin-inline-start: 0.5rem; // Additional spacing to prevent bullet collision (direct children only)
}
}

// A CTA button written on its own line in markdown — `<a class="btn btn-primary
// mt-3 …">` — becomes its own <p>, and that paragraph already carries the 16px
// rhythm margin. The `mt-3` utility then stacks a second 16px inside it, so the
// button ends up 32px below the preceding line but only 16px above the next one.
// Drop the utility when the button is alone in its paragraph and let the
// paragraph margin do the spacing, which puts the CTA on the same rhythm as
// every other block. `!important` is required because Primer's spacing
// utilities are themselves !important.
//
// `:only-child` is doing real work here — it is what keeps the two cases apart:
// - CTA callouts (`product:`/`permissions:` frontmatter) put the button after
// a <br> INSIDE the prose paragraph, so there is no paragraph margin above
// it and `mt-3` is the only thing separating it from the text.
// - The side-by-side Yes/No `.btn-outline` pairs are two buttons in one
// paragraph.
// Neither is an only child, so both keep their margin.
.markdown-body p > a.btn:only-child {
margin-top: 0 !important;
}

// @primer/css holds `.btn` at `white-space: nowrap`, which a button cannot
// honour and still stay inside a narrow column. The longest CTA label — "Set up
// a trial of GitHub Enterprise Cloud", 322px — is wider than the article column
// below a ~420px viewport and wider than the callout's text column below ~390px,
// so the button ran past the content edge and was clipped.
//
// Letting the label wrap fixes it with no breakpoint to guess at. An
// inline-block is shrink-to-fit — min(max-content, available) — so
// `white-space: normal` changes nothing until max-content exceeds the space
// available: at every width where the button already fits it still renders on
// one line, byte-identical. That also makes it self-correcting for longer
// translated labels and for the narrower column a callout gives the same button.
.markdown-body a.btn,
.permissions-statement a.btn,
.product-statement a.btn {
white-space: normal;

// Wrapping alone orphaned the trailing octicon on a line of its own: the
// space between the label <span> and the icon is a valid break point, and the
// label filled the first line exactly. Laying the button out as a flex row
// instead lets the label wrap within itself and keeps the icon beside it,
// vertically centred. At widths where nothing wraps the result is within a
// pixel of the inline-block it replaces: same 17px left inset, same 21px
// right inset, same 32px height, still one line. The `gap` below covers the
// one thing that does change.
display: inline-flex;
align-items: center;

// Flex layout eats the one thing that was separating the label from the icon.
// The markup is `<span>Label</span> {% octicon "link-external" %}`, and that
// literal space does survive Liquid and the markdown pipeline as a real text
// node — but a whitespace-only text node between two flex items is not itself
// a flex item, so no box is generated for it and the label ends up touching
// the icon. `gap` puts the space back.
//
// 4px rather than the measured width of that space glyph, because a space is
// font- and locale-dependent — it measures differently on two machines here —
// while 4px is the value Primer itself already uses between a button's icon
// and its label. The button ends up a fraction of a pixel wider than it was
// rather than most of a space narrower, on a number the design system owns.
//
// Only the label/icon gap is restored. Primer's `.btn .octicon` also carries
// `margin-right: 4px`, which assumes a LEADING icon and so lands outside the
// trailing icon on these CTAs, giving them 21px of inset on the right against
// 17px on the left. That asymmetry is what ships today, so it stays — zeroing
// it would restyle every CTA on the site, which is a different change from
// keeping a long label inside its column.
gap: 4px;

// The octicon is a flex item now, and flex items shrink before their container
// overflows. Once the label wraps, the icon is the only thing left to give, so
// the 16px glyph was rendering at 11px in a 240px callout column. It is a
// fixed-size icon; the label is what should absorb a narrow column.
.octicon {
flex-shrink: 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ versions:
Note that this page uses the `product` and `permissions` frontmatter property. So it should
result in a call out box rendered with two messages. But only if the version is *not*
Enterprise Server.

<!--
The only CTA button in the fixture content: the regression guard for the `:not(.btn)`
exclusion in src/frame/stylesheets/article-link-overrides.scss, scanned by axe via
src/fixtures/tests/playwright-a11y.spec.ts. It has to be `btn-primary` (only the filled
variant puts the label on a coloured fill, where the override's link blue fell under
4.5:1) and it has to be in the BODY, not in `product:` — the frontmatter callouts render
outside `.markdown-body`, and a CTA there would also break the byte-for-byte assertions
in src/fixtures/tests/permissions-callout.ts.
-->

<a href="https://github.com/pricing" target="_blank" class="btn btn-primary mt-3 mr-3 no-underline"><span>Sign up for {% data variables.product.prodname_pages %}</span> {% octicon "link-external" %}</a>
7 changes: 7 additions & 0 deletions src/fixtures/tests/playwright-a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ const SEARCH_TESTS = !!process.env.ELASTICSEARCH_URL
const pages: { [key: string]: string } = {
category: '/actions/category',
codeAnnotations: '/get-started/markdown/code-annotations',
// The only fixture page that renders a CTA button. A `.btn-primary` anchor is the
// one shape the brand article-link override can drive under 4.5:1 — its label sits
// on a coloured fill rather than the page background — which is exactly what it did
// before `:not(.btn)` was added to
// src/frame/stylesheets/article-link-overrides.scss. Without this entry that
// exclusion has no test at all.
ctaButton: '/get-started/foo/page-with-permissions-and-product-callout',
homepage: '/',
learningPath:
'/code-security/getting-started/quickstart?learn=foo_bar&learnProduct=code-security',
Expand Down
8 changes: 7 additions & 1 deletion src/frame/components/article/ArticleInlineLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ export const ArticleInlineLayout = ({
)}

{introCallOuts && (
<div style={{ gridArea: 'intro' }} className="f4 mb-4">
// `mt-4` (24px) matches the gap the grid layout gets from
// .belowIntroPlacement's own bottom margin. It is needed here because
// this layout puts the callouts in a separate wrapper from the intro,
// so the copy-markdown control is the last child of ITS wrapper and
// that rule cannot reach across. Without it the control sat flush on
// the callout box's top border.
<div style={{ gridArea: 'intro' }} className="f4 mt-4 mb-4">
{introCallOuts}
</div>
)}
Expand Down
33 changes: 32 additions & 1 deletion src/frame/components/article/ViewMarkdownButton.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,37 @@
// The control's placement below the article lede. The margin sets the gap from
// the lede — the Lead's own `mb-3` collapses into it, so this value wins — while
// the header's bottom padding provides the space down to the rule beneath.
//
// Both gaps are spelled as Primer's step-4 spacer rather than a bare `1.5rem`.
// The bottom one has to be: the inline layout sets the same gap from TSX with
// `mt-4`, which compiles to exactly this `var(--base-size-24, 24px)`, and
// `1.5rem` and `mt-4` grep as nothing in common. The top one follows so that
// "match the gap above" below stays literally true. This is the one token here
// that is not `--brand-*`, and it still resolves: brand's main.css defines the
// whole `--base-size-*` scale at `:root` (@primer/primitives' size.css, its
// other home, is one of the files this app never imports).
.belowIntroPlacement {
margin-top: 1.5rem;
margin-top: var(--base-size-24, 24px);

// ...but the header's bottom padding only reaches the control when the
// control is the last thing in the header. On pages that also render intro
// callouts — the "Who can use this feature?" box, or the platform/tool
// pickers — those are siblings that follow it, and the control sat flush
// against the box's top border with no gap at all. Match the gap above, so
// the control is evenly spaced between the lede and whatever follows.
//
// `:not(:last-child)` keeps this off the pages where nothing follows, which
// already get their spacing from the header padding and would otherwise gain
// a second 24px.
//
// This rule only ever fires in the GRID layout, where ArticlePage hands the
// control and the callouts to `intro` as siblings. The inline layout puts
// them in two separate wrappers, so the control is the last child of its own
// and this selector cannot reach across — ArticleInlineLayout.tsx sets the
// identical gap by hand with `mt-4` there instead. The two are one visual gap
// and have to stay equal; nothing enforces that but this note and the shared
// token spelling, so change both or neither.
&:not(:last-child) {
margin-bottom: var(--base-size-24, 24px);
}
}
12 changes: 10 additions & 2 deletions src/frame/stylesheets/article-link-overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@
// semantic pair keeps this independent of any ancestor that re-maps the
// component token — breadcrumbs-overrides.scss does exactly that.
//
// Two exclusions, both because this selector outweighs the rules that
// Three exclusions, all because this selector outweighs the rules that
// currently keep those anchors uncoloured:
// - `[href]` — @primer/css holds `.markdown-body a:not([href])` at
// `color: inherit`, for the bare named anchors markdown emits.
// - `:not(.heading-link)` — heading anchors wrap the entire heading text,
// and headings.scss holds them at `color: unset`.
a[href]:not(.heading-link) {
// - `:not(.btn)` — CTA buttons in content are plain anchors carrying
// @primer/css's button classes (`<a class="btn btn-primary">`), so they
// match this rule too. `.btn-primary`'s own `color` is only (0,1,0)
// against this selector's (1,4,1), so without the exclusion the label
// painted brand link-blue on the green button — #005dd5 on #1f883d is
// 1.31:1, well under the 4.5:1 WCAG AA floor
// (github/technical-content#7679). Excluding `.btn`
// hands every button variant back to its own Primer colour tokens.
a[href]:not(.heading-link):not(.btn) {
// Fallback literals are brand's LIGHT values, not Primer's.
color: var(--brand-color-text-link-rest, #005dd5);

Expand Down
Loading