diff --git a/dotcom-rendering/src/components/DirectoryPageNav.island.tsx b/dotcom-rendering/src/components/DirectoryPageNav.island.tsx index c661f97f8be..28e2379a3b9 100644 --- a/dotcom-rendering/src/components/DirectoryPageNav.island.tsx +++ b/dotcom-rendering/src/components/DirectoryPageNav.island.tsx @@ -304,12 +304,7 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => { }); const navWeb = css({ - '&': css( - grid.paddedContainer, - grid.verticalRules({ - color: borderColor, - }), - ), + '&': css(grid.paddedContainer, grid.outerRules(borderColor)), }); const largeLinkStyles = css({ diff --git a/dotcom-rendering/src/components/GalleryCaption.tsx b/dotcom-rendering/src/components/GalleryCaption.tsx index 26a71ba6205..9b5cf754a00 100644 --- a/dotcom-rendering/src/components/GalleryCaption.tsx +++ b/dotcom-rendering/src/components/GalleryCaption.tsx @@ -31,34 +31,10 @@ const styles = css` ${between.desktop.and.leftCol} { ${grid.column.right} - - position: relative; /* allows the ::before to be positioned relative to this */ - - &::before { - content: ''; - position: absolute; - left: -10px; /* 10px to the left of this element */ - top: 0; - bottom: 0; - width: 1px; - background-color: ${palette('--article-border')}; - } } ${from.leftCol} { ${grid.column.left} - - position: relative; /* allows the ::before to be positioned relative to this */ - - &::after { - content: ''; - position: absolute; - right: -11px; - top: -12px; - bottom: 0; - width: 1px; - background-color: ${palette('--article-border')}; - } } `; diff --git a/dotcom-rendering/src/components/GalleryImage.tsx b/dotcom-rendering/src/components/GalleryImage.tsx index 5ce317dcd10..5c849fc37d5 100644 --- a/dotcom-rendering/src/components/GalleryImage.tsx +++ b/dotcom-rendering/src/components/GalleryImage.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; import { isUndefined } from '@guardian/libs'; -import { from, space, until } from '@guardian/source/foundations'; +import { between, from, space, until } from '@guardian/source/foundations'; import { grid } from '../grid'; import { type ArticleFormat } from '../lib/articleFormat'; import { getImage } from '../lib/image'; @@ -23,7 +23,7 @@ type Props = { const styles = css` ${grid.paddedContainer} - ${grid.verticalRules()} + ${grid.outerRules()} grid-auto-flow: row dense; background-color: ${palette('--article-inner-background')}; @@ -33,10 +33,17 @@ const styles = css` } ${from.desktop} { - &:first-of-type { + &:first-of-type > * { padding-top: ${space[3]}px; } } + + ${between.desktop.and.leftCol} { + ${grid.centreRule(2)} + } + ${from.leftCol} { + ${grid.centreRule(1)} + } `; const galleryBodyImageStyles = css` diff --git a/dotcom-rendering/src/grid.ts b/dotcom-rendering/src/grid.ts index f60ae0ffc5d..50d462bc358 100644 --- a/dotcom-rendering/src/grid.ts +++ b/dotcom-rendering/src/grid.ts @@ -87,42 +87,71 @@ const paddedContainer = ` // ----- Vertical Rules ----- // -type VerticalRuleOptions = { - centre?: boolean; - color?: string; -}; - /** - * Render Guardian grid vertical rules. + * CSS for a centre vertical rule anchored to the top of the nth child of the + * grid container. + * + * The rule is self-contained on the nth child element rather than on the grid + * container, so that `top: 0` aligns to that element's top edge. `bottom` + * uses a large negative value to extend the rule down to the container's + * bottom, so `overflow: hidden` is applied to the container. * - * Left and right rules are always present. - * A centre rule can optionally be enabled. + * @example + * css` + * ${grid.container} + * ${grid.verticalRules()} + * ${grid.centreRule(1)} * - * Usage: - * css([grid.container, grid.verticalRules()]) - * css([grid.container, grid.verticalRules({ centre: true })]) + * ${from.leftCol} { + * ${grid.centreRule(3)} + * } + * ` */ -const optionalCentreRule = `/* CENTRE RULE */ - & > *:first-child::before { - grid-column: centre-column-start; - transform: translateX(-${columnGap}); - ${fromBreakpoint.leftCol} { - transform: translateX(calc(-${columnGap} / 2)); - } +const centreRule = (n: number, color?: string): string => `/* CENTRE RULE */ + overflow: hidden; + + & > *:nth-child(${n}) { + position: relative; + + &::before { + position: absolute; + top: 0; + bottom: -9999px; + width: 1px; + background-color: ${color ?? palette('--article-border')}; + content: ''; + grid-column: centre-column-start; + transform: translateX(-${columnGap}); + + ${fromBreakpoint.desktop} { + transform: translateX(calc(-${columnGap} / 2)); + } + } }`; -const verticalRules = (options: VerticalRuleOptions = {}): string => ` +/** + * CSS for the left and right Guardian grid vertical rules. + * + * Use `grid.centreRule` separately to add a centre rule anchored to a + * specific child element. + * + * @example + * css` + * ${grid.container} + * ${grid.verticalRules()} + * ` + */ +const outerRules = (color?: string): string => ` ${fromBreakpoint.tablet} { position: relative; &::before, - &::after - ${options.centre ? ', & > *:first-child::before' : ''} { + &::after { position: absolute; top: 0; bottom: 0; width: 1px; - background-color: ${options.color ?? palette('--article-border')}; + background-color: ${color ?? palette('--article-border')}; content: ''; } @@ -145,9 +174,7 @@ const verticalRules = (options: VerticalRuleOptions = {}): string => ` grid-column: centre-column-end; } } - - ${options.centre ? optionalCentreRule : ''} -`; + }`; // ----- API ----- // @@ -248,8 +275,17 @@ const grid = { * breakpoint. */ mobileColumnGap, - - verticalRules, + /** + * CSS for the left and right vertical rules. Use `grid.centreRule` + * separately to add a centre rule anchored to a specific child element. + */ + outerRules, + /** + * CSS for a centre vertical rule anchored to the top of the nth child of + * the grid container. Can be called multiple times within different + * breakpoint contexts to vary which child the rule anchors to. + */ + centreRule, } as const; // ----- Exports ----- // diff --git a/dotcom-rendering/src/layouts/GalleryLayout.tsx b/dotcom-rendering/src/layouts/GalleryLayout.tsx index b922146c1c9..c00dccf3a5e 100644 --- a/dotcom-rendering/src/layouts/GalleryLayout.tsx +++ b/dotcom-rendering/src/layouts/GalleryLayout.tsx @@ -579,7 +579,7 @@ const BodyAdSlot = (props: { const WebAdSlot = (props: { adIndex: number }) => (